使用MiniTest检查模型上的方法调用

如果我使用RSpec,我可以测试是否正在调用方法:

expect(obj).to receive(:method) 

MiniTest中的等价物是什么? 我有一个模型Post ,它有一个before_validation回调,它运行一个方法create_slug 。 在我的测试test/models/post_test.rb我想确保在调用post.save时调用create_slug方法。

Minitest :: Spec文档说我可以使用方法must_send来检查方法是否被调用。 但是,当我尝试@post.must_send :create_slug我收到以下错误:

 NoMethodError: undefined method `must_send' for # 

我在test_helper.rb文件中包含Minitest :: Spec:

 ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' require 'minitest/spec' class ActiveSupport::TestCase # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests # -- they do not yet inherit this setting fixtures :all # Add more helper methods to be used by all tests here... end 

我的测试摘录:

 describe Post do before do @post = FactoryGirl.build(:post) end describe "when saving" do it "calls the create_slug method before validation" do @post.must_send :create_slug @post.save end end end 

你在这里要求的是部分嘲笑。 这意味着你有一个真实的对象,你想模拟一个方法并validation它被调用。 Minitest :: Mock不支持开箱即用的部分模拟,但我会尽力向您展示如何实现这一目标。 在“部分模拟”上进行一两次搜索,看看人们对此有什么看法。

支持部分模拟的最简单方法是使用不同的模拟库,如Mocha 。 只需在您的Gemfile中添加gem "mocha"即可。

 describe Post do before do @post = FactoryGirl.build(:post) end describe "when saving" do it "calls the create_slug method before validation" do @post.expects(:create_slug).returns(true) @post.save end end end 

但是如果你真的想使用Minitest :: Mock,有一种方法可以让它发挥作用。 它需要一个新的模拟对象并使用ruby重新定义create_slug方法。 哦,还有全局变量。

 describe Post do before do @post = FactoryGirl.build(:post) end describe "when saving" do it "calls the create_slug method before validation" do $create_slug_mock = Minitest::Mock.new $create_slug_mock.expect :create_slug, true def @post.create_slug $create_slug_mock.create_slug end @post.save $create_slug_mock.verify end end end 

哇。 那很难看。 看起来像一个疏忽,对吗? 为什么Minitest会使部分嘲讽变得如此困难和丑陋? 这实际上是一个不同问题的症状。 问题不是“我如何使用部分模拟?”,问题是“我如何测试代码的预期行为?” 这些测试正在检查实现。 如果重命名create_slug方法怎么办? 或者,如果您将从回调中创建slug的机制更改为其他内容,该怎么办? 这将要求此测试也发生变化。

相反,如果您的测试只检查了预期的行为呢? 然后,您可以重构代码并更改您的实现,而不会破坏测试。 那会是什么样的?

 describe Post do before do @post = FactoryGirl.build(:post) end describe "when saving" do it "creates a slug" do @post.slug.must_be :nil? @post.save @post.slug.wont_be :nil? end end end 

现在我们可以自由地更改实现而无需更改测试。 测试涵盖了预期的行为,因此我们可以重构和清理代码而不会破坏所述行为。 人们问为什么Minitest不支持部分嘲笑。 这就是为什么。 你很少需要它们。

为此,Minitest有一个.expect :call ,它允许你检查方法是否被调用:

 describe Post do before do @post = FactoryGirl.build(:post) end describe "when saving" do it "calls the create_slug method before validation" do mock_method = MiniTest::Mock.new mock_method.expect :call, "return_value", [] @post.stub :create_slug, mock_method do @post.save end mock_method.verify end end end 

如果调用@ post.create_slug,测试将通过。 否则测试将引发MockExpectationError。

不幸的是,这个function没有很好地记录。 我从这里找到了这个答案: https : //github.com/seattlerb/minitest/issues/216