Custom error messages in RSpec or Cucumber steps

Sometimes you have a test expectation but actually want a better error message in case of a failure. Here is how to do that.

Background

Consider this test:

expect(User.last).to be_present

In case of an error, it will fail with a not-so-helpful error message:

expected present? to return true, got false (Spec::Expectations::ExpectationNotMetError)

Solution

That can be fixed easily. RSpec expectations allow you to pass an error message like this:

expect(User.last).to be_present, 'Could not find a user!'

Now your test will fail with an actually helpful error message:

Could not find a user! (Spec::Expectations::ExpectationNotMetError)

Note that you may also pass a block, if necessary.

Why is this cool?

One might argue why we would want to have better error messages for test expectations when our tests are supposed to be always passing. Well, what if they don't pass at some point? Have you ever had to dig into a test just to find out why it was failing? If that test had actually told what it was expecting, you did not have to do it.

Even more so: Consider an expectation that is actually more complex than the one above. Maybe you are looking at an array of items and want to check only a subset of them. Instead of a simple true/false error message, you could also have a message that shows what the expectation was working on, and what failed:

Found 0 instead of 2 matching events:
[]
All events:
[{"gtm.start"=>1234567890, "event"=>"gtm.js"}, {"event"=>"gtm.dom"}, {"event"=>"gtm.load"}]
(Spec::Expectations::ExpectationNotMetError)

And the best thing: Since you are using RSpec expectations in your Cucumber steps most of the time, you can do the same there.

Caveats

If you are using the legacy should syntax, you can't put custom error messages when comparing equality with ==. For that, you might want to use eq instead:

foo.should == 42, 'Not a valid answer' # does NOT work
foo.should eq(42), 'Not a valid answer'

Inequality is trickier, and uglier. You might want to wrap your expectation and compare against a boolean, maybe like this:

expect { foo > 23 }.to be_true, 'Not a valid answer'

If things get too tricky, you could be better off writing a custom RSpec matcher.

Arne Hartherz over 7 years ago
This website uses short-lived cookies to improve usability.
Accept or learn more