Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a less intrusive alternative to Rspec's `should_receive`?

Tags:

ruby

rspec

In writing Rspec tests, I'm often frustrated with should_receive. I'd like to know if there's a less intrusive alternative.

For example:

describe "making a cake" do
  it "should use some other methods" do
    @baker.should_receive(:make_batter)
    @baker.make_cake
  end
end

The call to should_receive is a good description, but it breaks my code, because should_receive works by masking the original method, and make_cake can't proceed unless make_batter actually returns some batter. So I change it to this:

@baker.should_receive(:make_batter).and_return(@batter)

This is ugly because:

  • It looks like I'm testing that make_batter correctly returns @batter, but I'm actually forcing the fake version of make_batter to return that.
  • It forces me to separately set up @batter
  • If make_batter has any important side effects (which could be a code smell, I suppose) I have to make those happen, too.

I wish that should_receive(:make_batter) would verify the method call and pass it on to the original method. If I wanted to stub its behavior for better isolation testing, I would do so explicitly: @baker.stub(:make_batter).and_return(@batter).

Is there a way to do something like should_receive without preventing the original method call? Is my problem a symptom of bad design?

like image 636
Nathan Long Avatar asked Aug 28 '12 12:08

Nathan Long


3 Answers

It looks like the nicer API to delegate to the original method that Myron Marston alluded to has actually been added in rspec-mocks v2.12.0

  • Add and_call_original which delegates to the original method.

So now you can simply do this any time you "want to set a message expecation without interfering with how the object responds to the message":

@baker.should_receive(:make_batter).and_call_original

Thanks for adding this, Myron.

like image 50
Tyler Rick Avatar answered Nov 13 '22 03:11

Tyler Rick


You can have should_receive run the original method like so:

@baker.should_receive(:make_batter, &@baker.method(:make_batter))

Both should_receive and stub support passing a block implementation (that is eval'd when the method is called). &@baker.method(:make_batter) gets a handle to the original method object, and passes it along as the block implementation.

FWIW, we'd like to provide a nicer API to delegate to the original method (see this issue), but it's difficult to add that functionality without breaking backwards compatibility.

like image 24
Myron Marston Avatar answered Nov 13 '22 03:11

Myron Marston


You're having this problem with should_receive because you're testing implementation details of the make_cake method. When writing tests you should only focus on behavior and not on a sequence of internal method calls. Otherwise refactoring your code later would result in a huge refactoring of all your tests as well.

Mocks and Stubs come in handy when you want to test your classes in isolation. When writing unit tests your test subject should be isolated from any other object. Both act as a stand-in when you're working with multiple objects at once. In this case you can use should_receive to ensure that your test subject correctly delegates some task to another object by calling a method.

like image 6
Benedikt Deicke Avatar answered Nov 13 '22 03:11

Benedikt Deicke