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

Iai*_*ain 4 ruby tdd ruby-on-rails minitest

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

expect(obj).to receive(:method)
Run Code Online (Sandbox Code Playgroud)

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

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

NoMethodError: undefined method `must_send' for #<Post:0x007fe73c39c648>
Run Code Online (Sandbox Code Playgroud)

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

我的测试摘录:

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

blo*_*age 10

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

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

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

但是如果你真的想使用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
Run Code Online (Sandbox Code Playgroud)

哇.那很难看.看起来像一个疏忽,对吗?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
Run Code Online (Sandbox Code Playgroud)

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