Mocha: How to add expectation of a method when there are multiple invocations with different parameters
Asked Answered
G

3

16

I have a Rails controller action to test. In that action, a method User.can? is invoked several times with different parameters. In one of the test case for it, I want to make sure that User.can?('withdraw') is invoked. But I don't care about invocations of User.can? with other parameters.

def action_to_be_tested
  ...
  @user.can?('withdraw')
  ...
  @user.can?('deposit')
  ...
end

I tried below in the test:

User.any_instance.expects(:can?).with('withdraw').at_least_once.returns(true)

But the test failed with message indicating unexpected invocation of User.can?('deposit'). If I add another expectation with parameter 'deposit', the test passed. But I am wondering if there are any ways such that I could just focus on the invocation with 'withdraw' parameter (because other invocations are irrelevant to this test case).

Gader answered 28/3, 2012 at 0:29 Comment(0)
G
19

I just found a workaround, by stubbing out invocations with irrelevant parameters:

User.any_instance.expects(:can?).with('withdraw').at_least_once.returns(true)
User.any_instance.stubs(:can?).with(Not(equals('withdraw')))

http://mocha.rubyforge.org/classes/Mocha/ParameterMatchers.html#M000023

Gader answered 28/3, 2012 at 4:52 Comment(2)
I was aware the API supported literal values (direct comparisons to variables), or yielding to the block, but I'm not sure where the Not(...) construct comes from. Could you speak to that in more detail?Thrice
@Thrice Not() comes from the Mocha::ParameterMatchers that are used with with(). They can be camel case like HasKey() or snake case like has_key(). Not has to be camel case because it's a keyword, though.Flameout
K
24

You can pass a block to with and have that block inspect the arguments. Using that, you can construct a list of expected invocations:

invocations = ['withdraw', 'deposit']
User.any_instance.expects(:can?).at_most(2).with do |permission|
  permission == invocations.shift
end

Each time can? is called, Mocha will yield to the block. The block will pull the next value off the list of expected invocations and check it against the actual invocation.

Koerner answered 19/3, 2014 at 0:3 Comment(2)
This should be the correct answer, as it does exactly what is wanted.Booster
This hasn't work for me, no matter what. The marked answer below worked though.Gastronomy
G
19

I just found a workaround, by stubbing out invocations with irrelevant parameters:

User.any_instance.expects(:can?).with('withdraw').at_least_once.returns(true)
User.any_instance.stubs(:can?).with(Not(equals('withdraw')))

http://mocha.rubyforge.org/classes/Mocha/ParameterMatchers.html#M000023

Gader answered 28/3, 2012 at 4:52 Comment(2)
I was aware the API supported literal values (direct comparisons to variables), or yielding to the block, but I'm not sure where the Not(...) construct comes from. Could you speak to that in more detail?Thrice
@Thrice Not() comes from the Mocha::ParameterMatchers that are used with with(). They can be camel case like HasKey() or snake case like has_key(). Not has to be camel case because it's a keyword, though.Flameout
G
3

A simpler version from @Innerpeacer version would be:

User.any_instance.stubs(:can?).with(any_parameters)
User.any_instance.expects(:can?).with('withdraw')
Gastronomy answered 29/1, 2021 at 12:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.