Dans mon test, je veux bloquer une réponse standard pour toute instance d'une classe.
Cela pourrait ressembler à quelque chose comme:
Book.stubs(:title).any_instance().returns("War and Peace")
Ensuite, chaque fois que j'appelle @book.title
il renvoie "Guerre et paix".
Existe-t-il un moyen de le faire dans MiniTest? Si oui, pouvez-vous me donner un exemple d'extrait de code?
Ou ai-je besoin de quelque chose comme du moka?
MiniTest prend en charge les Mocks mais les Mocks sont exagérés pour ce dont j'ai besoin.
# Create a mock object
book = MiniTest::Mock.new
# Set the mock to expect :title, return "War and Piece"
# (note that unless we call book.verify, minitest will
# not check that :title was called)
book.expect :title, "War and Piece"
# Stub Book.new to return the mock object
# (only within the scope of the block)
Book.stub :new, book do
wp = Book.new # returns the mock object
wp.title # => "War and Piece"
end
Si vous êtes intéressé par le stubbing simple sans bibliothèque moqueuse, il est assez facile de le faire dans Ruby:
class Book
def avg_Word_count_per_page
arr = Word_counts_per_page
sum = arr.inject(0) { |s,n| s += n }
len = arr.size
sum.to_f / len
end
def Word_counts_per_page
# ... perhaps this is super time-consuming ...
end
end
describe Book do
describe '#avg_Word_count_per_page' do
it "returns the right thing" do
book = Book.new
# a stub is just a redefinition of the method, nothing more
def book.Word_counts_per_page; [1, 3, 5, 4, 8]; end
book.avg_Word_count_per_page.must_equal 4.2
end
end
end
Si vous voulez quelque chose de plus compliqué comme écraser toutes les instances d'une classe, alors c'est aussi assez facile à faire, il vous suffit d'être un peu créatif:
class Book
def self.find_all_short_and_unread
repo = BookRepository.new
repo.find_all_short_and_unread
end
end
describe Book do
describe '.find_all_short_unread' do
before do
# exploit Ruby's constant lookup mechanism
# when BookRepository is referenced in Book.find_all_short_and_unread
# then this class will be used instead of the real BookRepository
Book.send(:const_set, BookRepository, fake_book_repository_class)
end
after do
# clean up after ourselves so future tests will not be affected
Book.send(:remove_const, :BookRepository)
end
let(:fake_book_repository_class) do
Class.new(BookRepository)
end
it "returns the right thing" do
# Stub #initialize instead of .new so we have access to the
# BookRepository instance
fake_book_repository_class.send(:define_method, :initialize) do
super
def self.find_all_short_and_unread; [:book1, :book2]; end
end
Book.find_all_short_and_unread.must_equal [:book1, :book2]
end
end
end
J'utilise minitest pour tous mes tests Gems, mais je fais tous mes talons avec du moka, il pourrait être possible de tout faire en minitest avec Mocks (il n'y a pas de stubs ou autre chose, mais les mocks sont assez puissants), mais je trouve que mocha fait un excellent travail, si cela aide:
require 'mocha'
Books.any_instance.stubs(:title).returns("War and Peace")
Vous pouvez facilement stub les méthodes dans MiniTest
. Les informations sont disponibles sur github .
Donc, en suivant votre exemple et en utilisant le Minitest::Spec
style, voici comment vous devez stub les méthodes:
# - RSpec -
Book.stubs(:title).any_instance.returns("War and Peace")
# - MiniTest - #
Book.stub :title, "War and Peace" do
book = Book.new
book.title.must_equal "War and Peace"
end
C'est un exemple vraiment stupide mais au moins vous donne un indice sur la façon de faire ce que vous voulez faire. J'ai essayé ceci en utilisant MiniTest v2.5.1 qui est la version fournie avec Ruby 1.9 et il semble que dans cette version la méthode #stub n'était pas encore pris en charge, mais j'ai essayé avec MiniTest v3.0 et cela a fonctionné comme un charme.
Bonne chance et félicitations pour avoir utilisé MiniTest!
Edit: Il existe également une autre approche pour cela, et même si cela semble un peu hackish, c'est toujours une solution à votre problème:
klass = Class.new Book do
define_method(:title) { "War and Peace" }
end
klass.new.title.must_equal "War and Peace"
Vous ne pouvez pas faire cela avec Minitest. Cependant, vous pouvez bloquer n'importe quelle instance particulière:
book = Book.new
book.stub(:title, 'War and Peace') do
assert_equal 'War and Peace', book.title
end
Juste pour expliquer davantage réponse de @ panic , supposons que vous ayez une classe Book:
require 'minitest/mock'
class Book; end
Tout d'abord, créez un talon d'instance Book et faites-le retourner le titre souhaité (autant de fois que vous le souhaitez):
book_instance_stub = Minitest::Mock.new
def book_instance_stub.title
desired_title = 'War and Peace'
return_value = desired_title
return_value
end
Ensuite, faites en sorte que la classe Book instancie votre stub d'instance Book (uniquement et toujours, dans le bloc de code suivant):
method_to_redefine = :new
return_value = book_instance_stub
Book.stub method_to_redefine, return_value do
...
Dans ce bloc de code (uniquement), la méthode Book::new
Est tronquée. Essayons:
...
some_book = Book.new
another_book = Book.new
puts some_book.title #=> "War and Peace"
end
Ou, plus laconiquement:
require 'minitest/mock'
class Book; end
instance = Minitest::Mock.new
def instance.title() 'War and Peace' end
Book.stub :new, instance do
book = Book.new
another_book = Book.new
puts book.title #=> "War and Peace"
end
Alternativement, vous pouvez installer la gemme d'extension Minitest minitest-stub_any_instance
. (Remarque: lorsque vous utilisez cette approche, la méthode Book#title
Doit exister avant de la bloquer.) Maintenant, vous pouvez dire plus simplement:
require 'minitest/stub_any_instance'
class Book; def title() end end
desired_title = 'War and Peace'
Book.stub_any_instance :title, desired_title do
book = Book.new
another_book = Book.new
puts book.title #=> "War and Peace"
end
Si vous souhaitez vérifier que Book#title
Est invoqué un certain nombre de fois, procédez comme suit:
require 'minitest/mock'
class Book; end
book_instance_stub = Minitest::Mock.new
method = :title
desired_title = 'War and Peace'
return_value = desired_title
number_of_title_invocations = 2
number_of_title_invocations.times do
book_instance_stub.expect method, return_value
end
method_to_redefine = :new
return_value = book_instance_stub
Book.stub method_to_redefine, return_value do
some_book = Book.new
puts some_book.title #=> "War and Peace"
# And again:
puts some_book.title #=> "War and Peace"
end
book_instance_stub.verify
Ainsi, pour toute instance particulière, l'invocation de la méthode stubbed plus de fois que spécifié augmente MockExpectationError: No more expects available
.
De plus, pour toute instance particulière, avoir invoqué la méthode stubbed moins de fois que spécifié augmente MockExpectationError: expected title()
, mais uniquement si vous appelez #verify
Sur cette instance à ce stade.
Vous pouvez toujours créer un module dans votre code de test et utiliser l'inclusion ou l'extension à des classes ou des objets de patch de singe avec lui. par exemple (dans book_test.rb)
module BookStub
def title
"War and Peace"
end
end
Vous pouvez maintenant l'utiliser dans vos tests
describe 'Book' do
#change title for all books
before do
Book.include BookStub
end
end
#or use it in an individual instance
it 'must be War and Peace' do
b=Book.new
b.extend BookStub
b.title.must_equal 'War and Peace'
end
Cela vous permet de mettre en place des comportements plus complexes qu'un simple stub pourrait permettre
J'ai pensé partager un exemple que j'ai construit sur les réponses ici.
J'avais besoin de stub une méthode à la fin d'une longue chaîne de méthodes. Tout a commencé avec une nouvelle instance d'un wrapper API Paypal. L'appel dont j'avais besoin pour aboutir était essentiellement:
Paypal_api = Paypal::API.new
response = Paypal_api.make_payment
response.entries[0].details.payment.amount
J'ai créé une classe qui s'est retournée à moins que la méthode ne soit amount
:
Paypal_api = Class.new.tap do |c|
def c.method_missing(method, *_)
method == :amount ? 1.25 : self
end
end
Ensuite, je l'ai inséré dans Paypal::API
:
Paypal::API.stub :new, Paypal_api do
get '/Paypal_payment', amount: 1.25
assert_equal 1.25, payments.last.amount
end
Vous pouvez faire en sorte que cela fonctionne pour plusieurs méthodes en créant un hachage et en renvoyant hash.key?(method) ? hash[method] : self
.