Question

I want to stub a method with Mocha only when a specific parameter value is given and call the original method when any other value is given.

When I do it like this:

MyClass.any_instance.stubs(:show?).with(:wanne_show).returns(true)

I get an

unexpected invocation for MyClass.show?(:other_value)

I also know, that I can stub all parameters when writing the mock without the ´with´-call and then give my specific mock. But then I have to know the return value for every call, which is not the case :/

tldr; Is there a way to call the original method in a stub or to stub just specific parameters and leave the others?

Was it helpful?

Solution

I spent an hour today trying and failing to get Mocha to allow me to only stub a particular session variable, the way that Rspec allows with ease. While I couldn't figure out a way to make this work, I did figure out a hackish workaround that might help some people, depending on the circumstances.

My "solution" was to remove the session stub after the target instance variable had been grabbed:

ActionDispatch::Request::Session.any_instance.stubs(:delete).returns(state).then.returns(nonce).then.with do |sym|
  ActionDispatch::Request::Session.any_instance.unstub(:delete) if sym == :login_nonce
  true
end

The trick I'm using here is, by knowing the arguments that will be passed to session.delete in the first two calls made for a particular action, I can remove the stub after that second delete call (for login_nonce) has been made, so the session begins behaving like normal again.

Another potentially useful aspect of constructing a with block like this is that the block has the full context of the caller, so one can directly inspect or extract session contents within the block. That is, if you wanted a test to grab the value of the blah session key, you should be able to write something like

ActionDispatch::Request::Session.any_instance.stubs(:[]).with do |key|
  @blah = session[key] if key == :blah
  true
end

As best I can tell, the with block always has to return true, otherwise Mocha will throw an Minitest::Assertion: unexpected invocation exception, because it doesn't know what to do if it has stubbed a method but the argument passed in doesn't match an argument that it can handle. The fundamental problem seems to be that once one calls stubs on any_instance, you can no longer have Mocha return a value from the actual session instance (unlike Rspec, which allows falling back to the original object using and_call_original as in the linked answer above).

Hopefully someone can use build upon some of these ideas to fashion a more elegant answer in the future, but since almost 8 years have passed and there are no answers, I figure this might be a usable starting point.

OTHER TIPS

The answer depends on what exactly you're testing.

A few notes:

1) I always avoid using stubs.any_instance. You can be specific in your stubs/mocks, which prevents false testing positives.

2) I prefer using spies along with stubs, to actively assert that something has been called. We use the bourne gem for this purpose. The alternative is to use a mock, which implicitly tests if something is being called (e.g. will fail if it doesn't get called.

So your class-method might looks something like this (note, this is RSpec syntax):

require 'bourne'
require 'mocha'

it 'calls MyClass.show?(method_params)' do
  MyClass.stubs(:show?)

  AnotherClass.method_which_calls_my_class

  expect(MyClass).to have_received(:show?).with('parameter!')
end

class AnotherClass
  def self.method_which_calls_my_class
    MyClass.show?('parameter!')
  end
end

There are a lot of stub/spy examples here.

Hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top