如何在 MiniTest 中存根?

发布于 2024-12-02 01:21:55 字数 316 浏览 0 评论 0原文

在我的测试中,我想为类的任何实例存根预设响应。

它可能看起来像这样:

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

然后每当我调用 @book.title 时,它都会返回“战争与和平”。

有没有办法在 MiniTest 中做到这一点? 如果是,您能给我一个示例代码片段吗?

或者我需要摩卡之类的东西吗?

MiniTest 确实支持 Mocks,但 Mocks 对于我的需要来说太过分了。

Within my test I want to stub a canned response for any instance of a class.

It might look like something like:

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

Then whenever I call @book.title it returns "War and Peace".

Is there a way to do this within MiniTest?
If yes, can you give me an example code snippet?

Or do I need something like mocha?

MiniTest does support Mocks but Mocks are overkill for what I need.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

固执像三岁 2024-12-09 01:21:55
  # 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
  # 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
巷子口的你 2024-12-09 01:21:55

我使用 minitest 进行所有 Gems 测试,但使用 mocha 进行所有存根,也许可以使用 Mocks 在 minitest 中完成所有操作(没有存根或其他任何东西,但模拟非常强大),但我发现 mocha 做了干得好,如果有帮助的话:

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

I use minitest for all my Gems testing, but do all my stubs with mocha, it might be possible to do all in minitest with Mocks(there is no stubs or anything else, but mocks are pretty powerful), but I find mocha does a great job, if it helps:

require 'mocha'    
Books.any_instance.stubs(:title).returns("War and Peace")
落在眉间の轻吻 2024-12-09 01:21:55

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

If you're interesting in simple stubbing without a mocking library, then it's easy enough to do this in 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

If you want something more complicated like stubbing all instances of a class, then it is also easy enough to do, you just have to get a little creative:

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
暖树树初阳… 2024-12-09 01:21:55

您可以轻松地在 MiniTest 中存根类方法。该信息可在 github 获取。

因此,按照您的示例,并使用 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

这是一个非常愚蠢的示例,但至少为您提供了如何做您想做的事情的线索做。我使用 MiniTest v2.5.1 进行了尝试,这是 Ruby 1.9 附带的捆绑版本,似乎在此版本中尚未支持 #stub 方法,但随后我尝试使用 MiniTest v3.0,它的效果非常好。

祝您好运并祝贺您使用 MiniTest

编辑:还有另一种方法,尽管它看起来有点黑客,但它仍然是您问题的解决方案:

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

klass.new.title.must_equal "War and Peace"

You can easily stub class methods in MiniTest. The information is available at github.

So, following your example, and using the Minitest::Spec style, this is how you should stub methods:

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

This a really stupid example but at least gives you a clue on how to do what you want to do. I tried this using MiniTest v2.5.1 which is the bundled version that comes with Ruby 1.9 and it seems like in this version the #stub method was not yet supported, but then I tried with MiniTest v3.0 and it worked like a charm.

Good luck and congratulations on using MiniTest!

Edit: There is also another approach for this, and even though it seems a little bit hackish, it is still a solution to your problem:

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

klass.new.title.must_equal "War and Peace"
青柠芒果 2024-12-09 01:21:55

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

require 'minitest/mock'
class Book; end

首先,创建一个 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 实例存根(仅且始终在以下代码块内):

method_to_redefine = :new
return_value = book_instance_stub
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"
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"
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"
end

如果您想验证 Book#title code> 被调用一定次数,然后执行以下操作:

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

因此,对于任何特定实例,调用存根方法的次数超过指定次数会引发 MockExpectationError: No more Expects available

此外,对于任何特定实例,调用存根方法的次数少于指定的次数会引发 MockExpectationError:预期的 title(),但前提是您在该实例上调用 #verify观点。

Just to further explicate @panic's answer, let's assume you have a Book class:

require 'minitest/mock'
class Book; end

First, create a Book instance stub, and make it return your desired title (any number of times):

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

Then, make the Book class instantiate your Book instance stub (only and always, within the following code block):

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

Within this code block (only), the Book::new method is stubbed. Let's try it:

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

Or, most tersely:

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

Alternatively, you can install the Minitest extension gem minitest-stub_any_instance. (Note: when using this approach, the Book#title method must exist before you stub it.) Now, you can say more simply:

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

If you want to verify that Book#title is invoked a certain number of times, then do:

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

Thus, for any particular instance, invoking the stubbed method more times than specified raises MockExpectationError: No more expects available.

Also, for any particular instance, having invoked the stubbed method fewer times than specified raises MockExpectationError: expected title(), but only if you invoke #verify on that instance at that point.

别理我 2024-12-09 01:21:55

您无法对类的所有实例进行存根,但可以对给定对象的任何实例方法进行存根,如下所示:

require "minitest/mock"

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

You cannot stub all instances of a class, but you can stub any instance method of a given object like this:

require "minitest/mock"

book = Book.new
book.stub(:title, 'War and Peace') do
  assert_equal 'War and Peace', book.title
end
岛歌少女 2024-12-09 01:21:55

您始终可以在测试代码中创建一个模块,并使用包含或扩展来使用它来猴子修补类或对象。例如(在 book_test.rb 中)

module BookStub
  def title
     "War and Peace"
  end
end

现在您可以在测试中使用它

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

这允许您将比简单存根可能允许的更复杂的行为组合在一起

You can always create a module in your test code, and use include or extend to monkey-patch classes or objects with it. eg (in book_test.rb)

module BookStub
  def title
     "War and Peace"
  end
end

Now you can use it in your 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

This allows you to put together more complex behaviours than a simple stub might allow

旧竹 2024-12-09 01:21:55

我想我应该分享一个基于此处答案的示例。

我需要在一长串方法的末尾存根一个方法。这一切都始于 PayPal API 包装器的新实例。我需要存根的调用本质上是:

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

我创建了一个返回自身的类,除非该方法是 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) ,这不仅仅适用于一种方法?哈希[方法]:自身。

I thought I'd share an example that I built upon the answers here.

I needed to stub a method at the end of a long chain of methods. It all started with a new instance of a PayPal API wrapper. The call I needed to stub was essentially:

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

I created a class that returned itself unless the method was amount:

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

Then I stubbed it in to PayPal::API:

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

You could make this work for more than just one method by making a hash and returning hash.key?(method) ? hash[method] : self.

埖埖迣鎅 2024-12-09 01:21:55

有一个 Minitest 扩展可以做到这一点。

gem 安装 minitest-stub_any_instance

require 'minitest/stub_any_instance'

Book.stub_any_instance :title, 'War and Peace' do
# your code here
end

There's a Minitest extension to do precicely this.

gem install minitest-stub_any_instance

require 'minitest/stub_any_instance'

Book.stub_any_instance :title, 'War and Peace' do
# your code here
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文