Mocha: stubbing method with specific parameter but not for other parameters
Asked Answered
M

2

14

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?

Masqat answered 15/5, 2013 at 7:38 Comment(0)
J
3

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.

Jonme answered 22/3, 2021 at 0:52 Comment(1)
Thank you. I made this the accepted answer as it helps at least in most cases.Masqat
R
-1

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.

Rubbish answered 16/5, 2013 at 3:24 Comment(4)
You didn't show how to avoid using any_instance though. In my experience this requires additional mocking out of the instantiation code which may or may not be equally or even more error prone and brittle.Lyophobic
Also, it isn't so helpful to resort to rspec syntax.Jessen
this gem is outdatedMedley
I agree with @roman - I use RSpec Mocks exclusively. github.com/rspec/rspec-mocksRubbish

© 2022 - 2024 — McMap. All rights reserved.