Specifying failure message for RSpec expectations

April 12, 2016

When writing the Unit Testing ActiveRecord eager-loading blog post a couple months ago, I noticed that the test failures I was getting while writing were not very helpful. Initially, I had an assertion that looked like this:

expect(restaurant.association(:reviews)).to be_loaded

Since I was writing the test before the implementation — *cough* TDD isn’t dead *cough* — I got the expected failure when running it. However, the failure message was not that friendly after all:

$ rspec spec/controllers/restaurant_controller_spec.rb
Failures:

  1) RestaurantsController#index eager loads
     Failure/Error: expect(restaurant.association(:reviews)).to be_loaded
       expected `#<ActiveRecord::Associations::HasManyAssociation (...) >.loaded?` to return true, got false

After seeing this type of test failure, my first instinct was to replace the assertion with something a bit more explicit (and verbose) to get a better failure message:

expect(restaurant.association(:reviews).loaded?).to be true

Then, running the test looked like this:

$ rspec spec/controllers/restaurant_controller_spec.rb
Failures:

  1) RestaurantsController#index eager loads
     Failure/Error: expect(restaurant.association(:reviews).loaded?).to be true

       expected: true
            got: false

Which is better than what we had… but not by much. We can do a lot better than that by using a hidden little gem (um, in the metaphorical sense and not a rubygem) already provided to us by RSpec: passing the failure message as the second parameter to the to method in the whole expect(...).to expression.

expect(restaurant.association(:reviews)).to be_loaded, 'Restaurant reviews are not eager loaded'

Let’s run that test again and see what the failure looks like now:

$ rspec spec/controllers/restaurant_controller_spec.rb
Failures:

  1) RestaurantsController#index eager loads
     Failure/Error: expect(restaurant.association(:reviews).loaded?).to be_truthy
       Restaurant reviews are not eager loaded

That’s what I’m talking about! Now I can finally focus on making the test pass.


Notes

  • We could’ve achieved something similar with a custom matcher, but that seems a bit too much for this case