Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

ruby on rails - Check method call on model using MiniTest

If I was using RSpec I could test if a method is being called like so:

expect(obj).to receive(:method)

What is the equivalent in MiniTest? I have a model, Post, which has a before_validation callback which runs a method create_slug. In my test test/models/post_test.rb I want to ensure that the create_slug method is being called when calling post.save.

The Minitest::Spec documentation says that I can use a method must_send to check if a method is called. However, when I try @post.must_send :create_slug I receive the following error:

NoMethodError: undefined method `must_send' for #<Post:0x007fe73c39c648>

I am including Minitest::Spec in my test_helper.rb file:

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

An excerpt of my test:

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
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

What you are asking for here is a partial mock. This means you have a real object and you want to mock out one method and verify it was called. Minitest::Mock does not support partial mocks out of the box, but I'll try to show you how you can accomplish this anyway. Do a search or two on "partial mock" and see what folks have to say about it.

The easiest way to support partial mocks is to use a different mocking library like Mocha. Just add gem "mocha" to your Gemfile and you should be good to go.

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

But if you really want to use Minitest::Mock there is a way to make it work. It requires a new mock object and using ruby to redefine the create_slug method. Oh, and global variables.

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

Wow. That's ugly. Looks like an oversight, right? Why would Minitest make partial mocks so difficult and ugly? This is actually a symptom of a different problem. The question isn't "How do I use partial mocks?", the question is "How do I test the expected behavior of my code?" These tests are checking the implementation. What if you renamed the create_slug method? Or what if you changed the mechanism that created a slug from a callback to something else? That would require that this test change as well.

Instead, what if instead your tests only checked the expected behavior? Then you could refactor your code and change your implementation all without breaking the test. What would that look like?

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

Now we are free to change the implementation without changing the tests. The tests cover the expected behavior, so we can refactor and clean the code without breaking said behavior. Folks ask why Minitest doesn't support partial mocks. This is why. You very rarely need them.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...