In RSpec, is there a method equivalent to "unstub" but for "should_receive"?

Is there any method to remove any stubbing and mocking while using RSpec?

Example:

RestClient.should_receive(:delete).with("http://www.example.com")
...
... 

# this will remove the mocking of "should_receive" and 
# restore the proper "delete" method on "RestClient".
RestClient.mocking_reset

(mocking_reset is my ficticious name for the required functionality).

I know that there is the method "unstub" which resets "stubs" but not "should_receive"s.

So, is there any method equivalent to "unstub" but for "should_receive"?

Panayotis


Solution 1:

You can overwrite some previous mocking by:

expect(RestClient).to receive(:delete).and_call_original

Or if it is wasn't any kind of expectation, just a simple stub:

allow(RestClient).to receive(:delete).and_call_original

Remember there exists also expect_any_instance_of and allow_any_instance_of.

Solution 2:

At the moment (rspec-mocks 2.10.1) there is not a method equivalent to unstub but for should_receive. You can reset all stubs and mocks using rspec_reset, and you can also write dirty hacks to remove a particular expectation (which I wouldn't recommend).

The following is an example of removing all stubs and expectations on an object:

describe "resetting stubs and expectations with rspec_reset" do
  before do
    @person = mock('person')
    @person.should_receive(:poke)
  end

  it "should not fail when we reset all stubs and expectations" do
    @person.rspec_reset
  end
end

Note that this method is annotated in the rspec source code as @private, which means that you should avoid using it more than absolutely necessary and it may break in future versions of rspec without warning. It is, however, pretty widely used in the specs for rspec itself, so it seems less likely that this will be deprecated any time soon.

After digging through the rspec-mocks code a bit more, you can of course do something really nasty and tear out the one specific expectation yourself:

# Works in rspec 2.10.1
describe "removing an expectation with an ugly hack" do
  before do
    @person = mock('person')
    @person.should_receive(:poke)
  end

  it "should not fail after we hack rspec by violating every law of good programming, ever" do
    @person.instance_variable_get(:@mock_proxy).instance_variable_get(:@method_double)[:poke].clear
  end
end

This is really bad since it violates the encapsulation of the rspec test package and you shouldn't do it. Instead, if you really had a compelling reason to remove a particular expectation, the right thing to do would be to add a public method upstream in rspec-mocks that adds a parallel method to unstub but for removing specific expectations. It would probably be located here (note the definition of stub and unstub as well in this file):

https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/methods.rb

Before going out and using any of the suggestions above for removing the expectation you may want to consider if your specs really need to do this or if they should be refactored. Using should_receive is an assertion about your code, and usually you should try to create examples (ie it blocks) that only assert one thing. I would be very curious about why you're needing something like rspec_reset unless you're trying to do too much in global setup (eg before :each or before :all blocks), or if your examples are trying to do too much (ie. with multiple assertions in a single example).

The one exception to this may of course be in the test suite of rspec-mocks, where rspec_reset is used to reset state between examples that test the functionality of applying stubs and mocks. Unless you're doing that, chances are your tests could be improved to not rely on global resets of stubs and mocks on an object.

I hope that this helps and please do let me know if you think that there is a legitimate case for adding an equivalent method to unstub but for message expectations (ie., after using should_receive). If I'm convinced that this is an OK thing to do I would consider adding this method and proposing it to be added upstream. Maybe it would be called unset_expectation? Please also feel free to use the guidance into the code and internal structures above to put together a pull request for rspec-mocks on your own, and we'll see if it gets accepted to create a mocking equivalent to unstub.

Solution 3:

For RSpec >= 2.14, the accepted answer will no longer work.

I refer you to Bernhard Köhler's answer on this thread (reproduced below to safeguard against link rot): undefined method `rspec_reset' with rspec version 2.14

The reset method does not exist in RSpec 2.14. Instead, it is a helper method defined inside the spec_helper.rb file for the rspec-mocks project.

You can unstub your expect/allow invocations with:

RSpec::Mocks.proxy_for(object).reset

For RSpec 3.9.0, at least, I find that the above has been replaced with:

RSpec::Mocks.space.proxy_for(object).reset