Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

BetterReceive: A More Assertive Mock

One thing I’ve always liked about TDD is the ease with which it guides you through development. Given an outside-in approach, one failing test will lead you into another, and as you develop you dive deeper. Each time you make a new set of tests pass you step back to the earlier context and continue until you’ve driven out your story one test at a time. This all works seamlessly, with the exception of adding new methods to a class. For example:

class Developer; end

class Office
  attr_accessor :developers

Now, we want to tell the office how to run. Testing it with RSpec would look something like this:

describe Office do
  describe "#run" do
    it "gives the developers a break to play ping pong" do
      dev =
      office = [dev])

      dev.should_receive :break_for_ping_pong

This test fails because dev does not receive break_for_ping_pong. From here making our tests pass is easier than actually making our code work:

class Office
  attr_accessor :developers

  def run

We run the tests, and we’re done! Everything passes. Time for a ping pong break. Oh, wait… dev still doesn’t respond to break_for_ping_pong, but the tests pass because dev does receive break_for_ping_pong.

Due to the dynamic nature of Ruby and all its meta-programming goodness/complexity, RSpec does not try to predict whether or not objects respond to the methods that are mocked out. Sometimes this is for the best, but in cases like the one above it would be nice to recognize the missing behavior.

Enter BetterReceive. BetterReceive works like any other RSpec mock, with the exception that before mocking/stubbing a method there is an extra assertion that the object under test responds to the specified method.

So, after including ‘better_receive’ in the Gemfile, we can update our test to be more assertive:

      dev.better_receive :break_for_ping_pong

Now we have a properly failing test, so we put off the ping pong break and make the code pass:

class Developer
  def break_for_ping_pong
    # ...

More important than driving out new functionality, now that we have working code BetterReceive will catch bugs later on. Imagine break_for_ping_pong was called in other places in the code.

class Pair
  attr_accessor :developers

  def disagree

Changing the implementation of Pair may lead to a refactoring in the Developer model, for example moving break_for_ping_pong onto Pair. The other places in the code base that use break_for_ping_pong may go unnoticed due to oversight, lack of discoverability, or however your bugs slip in. Had we left should_receive in our test for Office the suite would continue improperly passing, harboring a new bug. Since dev still receives break_for_ping_pong, the should_receive assertion passes even though dev doesn’t respond to break_for_ping_pong. BetterReceive catches these regressions and alerts you before letting new changes get too far.

#responds_to? Considered Helpful

Ruby’s method_missing feature is the main thing that stood in the way of tools like RSpec from being able to consistently predict whether an object responds to a method. For this reason, Ruby 1.9.2 introduced responds_to_missing?. The folks over at Thoughtbot have a great post about why you should always define respond_to_missing? when overriding method_missing.

Without updating what an object responds to you are pitting two of Ruby’s biggest strengths against each other: Meta-Programming vs. Duck Typing. (Spoiler: Duck typing loses.) This tradeoff doesn’t have to be made. Many libraries do a good job of updating responds_to? when changing method_missing, but they don’t all. BetterReceive can help you determine which parts of your own code and which libraries you use that have not yet updated responds_to_missing?. Those libraries better receive some pull requests.

Ideally, I would like to see the responds_to? assertion become the default when mocking/stubbing. While there are other obstacles to overcome in making this possible, consistently updating responds_to? is the most important.

  1. Brian Butz says:

    I really like the clever name, it’s more a demand than an expectation.

    For me personally, I like to pop ‘driving out the #run’ off the stack before pushing ‘driving out #break_for_ping_pong.

    What I tend to do when using expectations that pass at a unit level but would not necessarily pass at the acceptance level is just rerun the acceptance test. I’d expect my acceptance tests to tell me that I called a method that actually wasn’t defined, and then essentially start with that same error in the next unit test I write, since the first unit failure message I should get is the method isn’t defined.

  2. Steve Ellis says:

    Interesting way of driving out tests. Using BetterReceive you’d have to add an empty #break_for_ping_pong method to get your #run tests green first. I think that’s the practice for statically typed languages. Still, I think test-drivability is a lesser benefit to prevention of bugs…

    I tend not to cover every method call with an acceptance test, leaving a fair amount of code covered on the unit test level. I’ve seen/heard of a couple projects that replaced all should_receive’s with better_receive and discovered bugs that acceptance/integration tests weren’t picking up. It’s not supposed to be a first line of defense as much as a safety net.

  3. Drew says:

    Interesting post, thanks for sharing.

    It seems to me that the problem you’re trying to solve is actually created by the way you’ve written the test. Your Office “unit” test is creating a Developer object and testing the implementation of it rather than testing the interface that a parameter offers.

    I think the standard way of approaching this problem would be to, instead of instantiating a new Developer object, send a mock object into with :break_for_ping_pong stubbed. This makes it explicit that your Office#run method depends on the :break_with_ping_pong method and decouples your test from the implementation of the collaborators.

    Obviously, after writing that test you’d want to write the unit test for that method on any other objects that could possibly be a collaborator to Office, in this case Developer. This way you don’t get caught being forced to write a method to satisfy another object’s unit test.

  4. Have you seen rspec-fire?

    It provides some similar functionality, but does so in a different manner, encouraging you to use a pure test double rather than a partial mock. I also like that it allows you to test things in isolation without the collaborator’s code being loaded.

    Anyhow, I like seeing other approaches to solving this problem, so nice work!

  5. Andrew Bruce says:

    Myron: I would go further and say that you shouldn’t be mocking a class, but a role that a particular concrete class’s instances might play. This approach encourages object reuse and decoupling, and forces some useful design constraints upon code that is yet to be written.

    A framework that requires you to specify the types to mock introduces a type dependency (albeit a fairly easy one to change). Shared examples, such as the ones in Sandi Metz’s POODR book, allow new types to adhere to an interface, which is more flexible than the rspec-fire approach. However, it does require you to remember to write these interface contract tests.

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *