Brian
Brian

Reputation: 7135

Mocha: Can I put a "never" expectation on a stubbed method WITH parameters?

My question is similar to this one: Mocha: stubbing method with specific parameter but not for other parameters

obj.expects(:do_something).with(:apples).never
perform_action_on_obj

The perform_action_on_obj will not call do_something(:apples) as I expect. However, it may call do_something(:bananas). If it does, I get an unexpected invocation failure.

My understanding is that since I placed never at the end of the expectation, it only applied to that specific modified expectation. However it appears that once I start mocking behavior on obj I've "screwed it up" in a sense.

How can I allow other invocations of the do_something method on obj?

EDIT: Here is a clear cut example that demonstrates my issue perfectly:

describe 'mocha' do
  it 'drives me nuts' do
    a = mock()
    a.expects(:method_call).with(:apples)

    a.lol(:apples)
    a.lol(:bananas) # throws an unexpected invocation
  end 
end

Upvotes: 6

Views: 4938

Answers (4)

Pat Newell
Pat Newell

Reputation: 2294

have you tried the block feature of with?

given that this:

obj.stubs(:do_something).with(:apples)

gives the same behavior as this:

obj.stubs(:do_something).with { |p| p == :apples }

you can flip the logic to get what you want:

obj.stubs(:do_something).with { |p| p != :apples }

calls to do_something with anything other than :apples will pass, whereas obj.do_something(:apples) will yield an unexpected invocation.

Upvotes: 3

Jon Garvin
Jon Garvin

Reputation: 1188

The accepted answer didn't work form me, so I figured out this work around that does in my case.

class NeverError < StandardError; end

obj.stubs(:do_something).with(:apples).raises(NeverError)

obj.do_something(:bananas)

begin
  obj.do_something(:apples)
rescue NeverError
  assert false, "expected not to receive do_something with apples"
end

There's room for improvement, but this gives the gist and should be easy to modify to fit most scenarios.

Upvotes: 1

charleyc
charleyc

Reputation: 1709

Here's a workaround using ParameterMatchers:

require 'test/unit'
require 'mocha/setup'

class MyTest < Test::Unit::TestCase
  def test_something
    my_mock = mock()
    my_mock.expects(:blah).with(:apple).never
    my_mock.expects(:blah).with(Not equals :apple).at_least(0)
    my_mock.blah(:pear)
    my_mock.blah(:apple)
  end
end

Result:

>> ruby mocha_test.rb 
Run options: 

# Running tests:

F

Finished tests in 0.000799s, 1251.6240 tests/s, 0.0000 assertions/s.

  1) Failure:
test_something(MyTest) [mocha_test.rb:10]:
unexpected invocation: #<Mock:0xca0e68>.blah(:apple)
unsatisfied expectations:
- expected never, invoked once: #<Mock:0xca0e68>.blah(:apple)
satisfied expectations:
- allowed any number of times, invoked once: #<Mock:0xca0e68>.blah(Not(:apple))


1 tests, 0 assertions, 1 failures, 0 errors, 0 skips

In general, I agree with you: this behavior is frustrating to work with and violates the principle of least surprise. It's also hard to extend the trick above to more general cases, since you'll have to write an increasingly complicated 'catchall' expression. If you want something more intuitive, I find RSpec's mocks to be quite nice.

Upvotes: 10

k_Dank
k_Dank

Reputation: 695

This is more of a hack than a real answer explaining mocha's behavior but perhaps the following will work?

obj.expects(:do_something).with(:apples).times(0)

Mocha will set the cardinality instance variable using times rather than exactly.

Upvotes: 1

Related Questions