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.