Just a couple of things that have caused a bit of head-scratching lately when writing RSpec specs using the built-in mocking framework.
Catching StandardError
Watch out if the code you’re testing catches StandardError
(of course you’re not catching Exception
, right?). Try this:
require 'rubygems' require 'spec' class Foo def self.foo Bar.bar rescue StandardError # do something here and don't re-raise end end class Bar def self.bar end end describe 'Calling a method that catches StandardError' do it 'calls Bar.bar' do Bar.should_receive :bar Foo.foo end end
Nothing particularly exciting there. Let’s run it and check that it passes:
$ spec foo.rb . Finished in 0.001862 seconds 1 example, 0 failures
However, what if we change the example to test the opposite behaviour?
describe 'Calling a method that catches StandardError' do it 'does NOT call Bar.bar' do Bar.should_not_receive :bar Foo.foo end end
$ spec foo.rb . Finished in 0.001865 seconds 1 example, 0 failures
Wait, surely they can’t both pass? Let’s take out the rescue and see what’s going on:
class Foo def self.foo Bar.bar end end
$ spec foo.rb F 1) Spec::Mocks::MockExpectationError in 'Calling a method that catches StandardError does NOT call Bar.bar'expected :bar with (no args) 0 times, but received it once ./foo.rb:6:in `foo' ./foo.rb:18: Finished in 0.002276 seconds 1 example, 1 failure
That’s more like it.
Of course, what’s really happening here is that Spec::Mocks::MockExpectationError
is a subclass of StandardError
, so is being caught and silently discarded by our method under test.
If you’re doing TDD properly, this won’t result in a useless test (at least not immediately), but it might cause you to spend a while trying to figure out how to get a failing test before you add the call to Foo.foo
(assuming the method with the rescue
already existed). Generally you can solve the problem by making the code a bit more selective about which exception class(es) it catches, but I wonder whether RSpec exceptions are special cases which ought to directly extend Exception
.
Checking receive counts on previously-stubbed methods
It’s quite common to stub a method on a collaborator in a before
block, then check the details of the call to the method in a specific example. This doesn’t work quite as you would expect if for some reason you want to check that the method is only called a specific number of times:
require 'rubygems' require 'spec' class Foo def self.foo Bar.bar Bar.bar end end class Bar def self.bar end end describe 'Checking call counts for a stubbed method' do before do Bar.stub! :bar end it 'only calls a method once' do Bar.should_receive(:bar).once Foo.foo end end
$ spec foo.rb . Finished in 0.001867 seconds 1 example, 0 failures
I think what’s happening here is that the mock object would normally receive an unexpected call, causing the
You can fix it, but it’s messy:
it 'only calls a method once' do Bar.send(:__mock_proxy).reset Bar.should_receive(:bar).once Foo.foo end
$ spec foo.rb F 1) Spec::Mocks::MockExpectationError in 'Checking call counts for a stubbed method only calls a method once'expected :bar with (any args) once, but received it twice ./foo.rb:23: Finished in 0.002542 seconds 1 example, 1 failure
Does anyone know a better way?
The full example code is in this gist.
3 replies on “A couple of rspec mocking gotchas”
Hey Kerry – you’ve uncovered a couple of nasty bugs here – would you kindly report them to http://rspec.lighthouseapp.com so we can talk there about how to get them fixed?
Thanks,
David
Tickets 830 and 831 raised. Sorry, should have done that earlier.
Thank you! This just saved me a lot of head scratching