Check method call on model using MiniTest

前端 未结 2 1126
故里飘歌
故里飘歌 2020-12-21 01:58

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?

相关标签:
2条回答
  • 2020-12-21 02:40

    For this purpose Minitest has a .expect :call which allows you to check if method is getting called:

    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
    

    If @post.create_slug was called, test will pass. Otherwise test will raise a MockExpectationError.

    Unfortunately this feature is not documented very well. I found this answer from here: https://github.com/seattlerb/minitest/issues/216

    0 讨论(0)
  • 2020-12-21 02:56

    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.

    0 讨论(0)
提交回复
热议问题