我如何在MiniTest中存根?

在我的testing中,我想为任何类的实例存根jar头响应。

它可能看起来像这样:

Book.stubs(:title).any_instance().returns("War and Peace") 

然后,每当我打电话@book.title它返回“战争与和平”。

有没有办法在MiniTest中做到这一点? 如果是的话,你可以给我一个示例代码片段?

或者我需要摩卡咖啡吗?

MiniTest支持Mocks,但是Mock对我所需要的东西是过度的。

  book = MiniTest::Mock.new book.expect :title, "War and Piece" Book.stub :new, book do wp = Book.new wp.title # => "War and Piece" end 

如果你在没有模拟库的简单存根中感兴趣,那么在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 

如果你想要一些比较复杂的东西,例如把一个class级的所有实例都存起来,那么做起来也很容易,你只需要有点创意:

 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 

我使用minitest进行所有的gemtesting,但是用摩卡做所有的存根(stub),也许可以用mock做所有的事情(没有存根或其他东西,但是模仿非常强大),但是我发现摩卡做了一个伟大的工作,如果有帮助:

 require 'mocha' Books.any_instance.stubs(:title).returns("War and Peace") 

您可以轻松地在MiniTest存根方法。 这些信息可以在github上find 。

所以,遵循你的例子,并使用Minitest::Spec风格,这是你应该如何存根方法:

 # - 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 

这是一个非常愚蠢的例子,但至less给你一个线索如何做你想做的事情。 我使用了MiniTest v2.5.1 ,它是Ruby 1.9自带的捆绑版本,看来在这个版本中,#stub方法还没有被支持,但是后来我用MiniTest v3.0试了一下,它的function就像一个魅力一样。

恭喜您使用MiniTest

编辑:还有另一种方法,即使它看起来有点hackish,它仍然是解决您的问题:

 klass = Class.new Book do define_method(:title) { "War and Peace" } end klass.new.title.must_equal "War and Peace" 

Minitest无法做到这一点。 但是,您可以存储任何特定的实例:

 book = Book.new book.stub(:title, 'War and Peace') do assert_equal 'War and Peace', book.title end 

为了进一步解释@ panic的答案,我们假设你有一个Book类:

 require 'minitest/mock' class Book; end 

首先,创build一个Book实例存根,并使其返回所需的标题(任意次数):

 book_instance_stub = Minitest::Mock.new def book_instance_stub.title desired_title = 'War and Peace' return_value = desired_title return_value end 

然后,让Book类实例化你的Book实例存根(仅在下面的代码块中,并且总是):

 return_value = book_instance_stub method_to_redefine = :new Book.stub method_to_redefine, return_value do 

在这个代码块(仅)中,Book :: new方法被删除。 让我们试试看:

  some_book = Book.new another_book = Book.new puts some_book.title #=> "War and Peace" puts some_book.title #=> "War and Peace" end 

或者,最简洁:

 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" puts book.title #=> "War and Peace" end 

或者,您可以安装Minitest扩展程序gem'minitest-stub_any_instance'。 (注意:这种方式在存根之前必须存在Book#title方法。)现在,可以更简单地说:

 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" puts book.title #=> "War and Peace" end 

如果您想validation书号标题被调用了一定次数,那么请:

 require 'minitest/mock' class Book; end desired_title = 'War and Peace' return_value = desired_title method = :title book_instance_stub = Minitest::Mock.new number_of_title_invocations = 2 number_of_title_invocations.times do book_instance_stub.expect method, return_value end return_value = book_instance_stub method_to_redefine = :new Book.stub method_to_redefine, return_value do some_book = Book.new puts some_book.title #=> "War and Peace" puts some_book.title #=> "War and Peace" end book_instance_stub.verify 

因此,对于任何特定的实例,调用比指定MockExpectationError: No more expects available方法引发MockExpectationError: No more expects available

另外,对于任何特定的实例,调用比指定less的次数的MockExpectationError: expected title()方法会引发MockExpectationError: expected title() ,但是只有当你在那个实例上调用#verify时。

您可以随时在testing代码中创build一个模块,并使用包含或扩展到猴子补丁类或对象。 例如(在book_test.rb中)

 module BookStub def title "War and Peace" end end 

现在你可以在你的testing中使用它

 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.nust_equal 'War and Peace' end 

这允许你把更复杂的行为放在一起,而不是一个简单的存根可能允许的

我想我会分享一个我在这里的答案build立的例子。

我需要在一个长链方法的末尾存根方法。 这一切都是从一个PayPal API包装的新实例开始的。 我需要存根的电话本质上是:

 paypal_api = PayPal::API.new response = paypal_api.make_payment response.entries[0].details.payment.amount 

我创build了一个返回自己的类,除非方法是amount

 paypal_api = Class.new.tap do |c| def c.method_missing(method, *_) method == :amount ? 1.25 : self end end 

然后我将其存入PayPal::API

 PayPal::API.stub :new, paypal_api do get '/paypal_payment', amount: 1.25 assert_equal 1.25, payments.last.amount end 

你可以通过做一个散列并返回hash.key?(method) ? hash[method] : self来使这个工作不仅仅是一种方法hash.key?(method) ? hash[method] : self hash.key?(method) ? hash[method] : self