Search code examples
ruby-on-railsrubyminitest

What is the best way to stub multiple class methods with Minitest?


I'm using Ruby 3.2.2 with Rails 7.0.5.

I'm writing a unit test some code while mocking/stubbing a utility class that uses various class methods.

It roughly looks like this:

class MyUtilityClass
  def self.foo
    # Does something...
  end

  def self.bar
    # Does something else...
  end
end

class MyTestTarget
  def run_foo
    MyUtilityClass.foo
  end
end 

I want to test that when I call run_foo it calls foo but not bar in MyUtilityClass. So I need to stub both foo and bar as part of my test.

I know how to stub methods like this:

test 'calls foo' do
  mock_foo = Minitest::Mock.new
  mock_foo.expect :call, nil, []
  MyUtilityClass.stub :foo, mock_foo do
    t = MyTestTarget()
    t.run_foo
  end
  mock_foo.verify
end

What is the most elegant/best practice way to test that bar() was not called as part of this test case?


Solution

  • Warning at the end.

    So, the ugly answer your actual question, I believe the only way with Minitest::Mock is to create a mock for your :bar and then assert that .verify DOES raise MockExpectationError.

    ...but the much nicer answer to your underlying question is that the awkward API and lack of features in the built-in mocking is why gems like mocha exist, and I'd strongly recommend it's worth switching to even if for just this one use case:

    test 'calls foo not bar' do
      MyUtilityClass.expects(:foo).once
      MyUtilityClass.expects(:bar).never
      MyTestTarget.new.run_foo
    end
    

    Warning

    Hopefully you know how fickle testing that something has NOT been called can be. Eg. if someone later adds a call to bar inside foo then because you're mocking foo in your test, then bar will still not be being called (in your test) even though it IS being called in your actual code.