Search code examples
ruby-on-railsrubyunit-testingminitest

Ruby on Rails Minitest multiple mocks on one test


I'm writing unit tests using Minitest with Ruby on Rails.

Occasionally I need to mock multiple things at once.

For example, when testing an action that triggers notifications to users, I might want to mock two external servers (example: SmsClient and EmailClient) in one go.

I can do this as follows:

test 'my test case' do
  SmsClient.stub :send, nil do
    EmailClient.stub :send, nil do
      get my_controller_url
    end
  end
end

This works, however I now need to mock one more class on the same call.

I'm concerned that my nesting of these stub calls is going to get out of control.

Question: Is there a way I can setup a mock on these services without using the nesting?

Note: These examples are just a hypothetical.


Solution

  • I don't know if there are any Minitest helpers to simplify this, but here is some core Ruby code that could reduce nesting.

    stubs = [
      proc { |block| proc { SmsClient.stub(:send, nil, &block) } },
      proc { |block| proc { EmailClient.stub(:send, nil, &block) } },
    ]
    
    tests = proc do
      # ...
    end
    
    stubs.reduce(:<<).call(tests).call
    # results in
    # (stubs[0] << stubs[1]).call(tests).call
    #
    # aka
    # stubs[0].call(stubs[1].call(tests)).call
    #
    # aka
    # SmsClient.stub(:send, nil) do
    #   EmailClient.stub(:send, nil) do
    #     # ...
    #   end
    # end
    

    To understand this answer you have to know that:

    some_method :a, :b do
      # block code
    end
    

    Can also be written as:

    block = proc do
      # block code
    end
    
    some_method(:a, :b, &block)
    

    Each item in the subs array is a proc, that accepts a block, and returns a new proc that is passed to another element.

    stubs.reduce(:<<) will create a composed proc that passes any argument to the last element, the return value of that proc is passed the the element before it.

    double = proc { |n| n + n }
    pow2   = proc { |n| n * n }
    
    operations = [pow2, double]
    operations.reduce(:<<).call(2) # the pow2 of the double of 2
    # pow2.call(double.call(2))
    # a = 2 + 2 = 4
    # b = a * a = 4 * 4 = 16
    #=> b = 16
    
    stubs.reduce(:<<).call(tests) # does the same
    # stubs[0].call(stubs[1].call(tests))
    # a = proc { EmailClient.stub(:send, nil, &tests) }
    # b = proc { SmsClient.stub(:send, nil, &a) }
    #=> b = proc { SmsClient.stub(:send, nil, &proc { EmailClient.stub(:send, nil, &tests) }) }
    

    Note that b is still a proc so we need to call .call again on the final return value.

    Here is an image to help you understand what is happening in the code above:

    explanation code

    Note that you could invert the nesting by changing :<< into :>>.


    You could define a helper to abstract away the common logic:

    def stub_all(*stubs, &test_block)
      stubs.map! do |subject, *stub_args|
        proc { |block| proc { subject.stub(*stub_args, &block) } }
      end
    
      stubs.reduce(:<<).call(test_block).call
    end
    
    stub_all(
      [SmsClient, :send, nil],
      [EmailClient, :send, nil],
    ) do
      # ...
    end