我如何在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
。