如何在MiniTest中存根?

Nic*_*ick 60 ruby stub minitest

在我的测试中,我想为任何类的实例存根固定响应.

它可能看起来像:

Book.stubs(:title).any_instance().returns("War and Peace")
Run Code Online (Sandbox Code Playgroud)

然后,每当我打电话,@book.title它就会返回"战争与和平".

有没有办法在MiniTest中执行此操作?如果是的话,你能给我一个示例代码片段吗?

或者我需要像摩卡这样的东西?

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

Pan*_*nic 34

  # 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
Run Code Online (Sandbox Code Playgroud)

  • 我觉得这很难读.目的不明确.虽然这对你的回答更多是对minitest的评论. (22认同)

Ell*_*ler 26

如果你对没有模拟库的简单存根感兴趣,那么在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
Run Code Online (Sandbox Code Playgroud)

如果你想要更复杂的东西,如存根类的所有实例,那么它也很容易做到,你只需要有点创意:

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
Run Code Online (Sandbox Code Playgroud)

  • 如果您在测试中重新定义方法,则该方法在测试完成后会立即还原还是保留为其他测试重新定义? (2认同)

dan*_*iel 23

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

require 'mocha'    
Books.any_instance.stubs(:title).returns("War and Peace")
Run Code Online (Sandbox Code Playgroud)


mon*_*ion 19

您可以轻松地存根方法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
Run Code Online (Sandbox Code Playgroud)

这是一个非常愚蠢的例子,但至少可以为您提供如何做您想做的事情的线索.我尝试使用MiniTest v2.5.1,这是Ruby 1.9附带的捆绑版本,似乎在这个版本中#stub方法尚不支持,但后来我尝试使用MiniTest v3.0,它就像一个魅力.

祝你好运并祝贺使用MiniTest!

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

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

klass.new.title.must_equal "War and Peace"
Run Code Online (Sandbox Code Playgroud)

  • 看起来像`Book.stub:title,"War and Peace"`只有在`title`是`book`的class_method时才有效.我无法重现与`any_instance`相同的行为,错误`NameError:undefined method` title '为'书' (12认同)
  • `stub`在`metaclass = class << self上运行; 自; 在MiniTest中按照[模拟库的来源](https://github.com/seattlerb/minitest/blob/master/lib/minitest/mock.rb#L170)结束.所以看来你只能存在单例方法; 否则,请使用完整的模拟对象. (4认同)

Chr*_*ris 14

你不能用Minitest做到这一点.但是,您可以存根任何特定实例:

book = Book.new
book.stub(:title, 'War and Peace') do
  assert_equal 'War and Peace', book.title
end
Run Code Online (Sandbox Code Playgroud)


Mar*_*ell 14

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

require 'minitest/mock'
class Book; end
Run Code Online (Sandbox Code Playgroud)

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

book_instance_stub = Minitest::Mock.new
def book_instance_stub.title
  desired_title = 'War and Peace'
  return_value = desired_title
  return_value
end
Run Code Online (Sandbox Code Playgroud)

然后,让Book类实例化您的Book实例存根(仅在以下代码块中):

method_to_redefine = :new
return_value = book_instance_stub
Book.stub method_to_redefine, return_value do
  ...
Run Code Online (Sandbox Code Playgroud)

在这个代码块(仅)中,Book :: new方法是存根的.我们来试试吧:

  ...
  some_book = Book.new
  another_book = Book.new
  puts some_book.title #=> "War and Peace"
end
Run Code Online (Sandbox Code Playgroud)

或者,最简洁:

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
Run Code Online (Sandbox Code Playgroud)

或者,您可以安装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
Run Code Online (Sandbox Code Playgroud)

如果要验证Book#title被调用了一定次数,那么执行:

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
Run Code Online (Sandbox Code Playgroud)

因此,对于任何特定实例,调用stubbed方法的次数比指定的次数多Book::new.

此外,对于任何特定实例,调用stubbed方法的次数少于指定的引用次数minitest-stub_any_instance,但前提是您在该实例上调用#verify.


Ste*_*ill 5

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

module BookStub
  def title
     "War and Peace"
  end
end
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

  • 测试最后应该撤消任何存根,因此它们不会污染其他测试。但是[在核心的ruby中没有办法](https://bugs.ruby-lang.org/issues/8967)取消包含/取消扩展模块。有几个本机扩展,例如。https://github.com/rosylilly/uninclude用于MRI。 (2认同)