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).

Vega
  • 27,856
  • 27
  • 95
  • 103
Innerpeacer
  • 1,321
  • 12
  • 20

3 Answers3

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.

Erin Call
  • 1,764
  • 11
  • 15
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

Innerpeacer
  • 1,321
  • 12
  • 20
  • I was aware [the API](http://gofreerange.com/mocha/docs/Mocha/Expectation.html#with-instance_method) 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? – Pysis Dec 02 '16 at 14:45
  • @Pysis `Not()` comes from the [Mocha::ParameterMatchers](http://gofreerange.com/mocha/docs/Mocha/ParameterMatchers.html) 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. – a paid nerd Oct 06 '17 at 21:02
3

A simpler version from @Innerpeacer version would be:

User.any_instance.stubs(:can?).with(any_parameters)
User.any_instance.expects(:can?).with('withdraw')
dferrazm
  • 643
  • 5
  • 10