How to Get More Detailed Information When RSpec Tests Fail



This content originally appeared on DEV Community and was authored by Takashi SAKAGUCHI

When an RSpec test fails, of course, the first thing we do is try to figure out why.
In a local environment, you might stop the process with binding.b (using byebug, pry, etc.) and investigate directly in the console. But what about those flaky tests that only fail on CI? Debugging them can be far more challenging.

In this article, I’ll share a small but useful technique: customizing failure messages in RSpec. This trick can help you even when debugging is hard in CI environments.

The second argument of expect(...).to matcher, message

You’ve probably written something like this before:

expect(actual).to eq(expected)

What’s less known is that the .to method actually takes a second argument for a failure message:

expect(actual).to eq(expected), "Not matching"

If the expectation fails, "Not matching" will be shown.
Even better—you can pass a block instead of a plain string:

expect(actual).to eq(expected), -> { "Not matching: actual=#{actual.inspect}" }

This block is evaluated only when the matcher fails, which means you can embed state information into the failure message—almost like custom logging.

Useful for debugging flaky tests in CI

Tests that fail only on CI are especially tricky since reproducing them locally can be impossible.
That’s where this technique shines. You can embed contextual data directly into the failure message:

expect(result).to eq("success"), -> {
  <<~MSG
    Result did not match.
    Actual value: #{result.inspect}
    User ID: #{user.id}
    Request params: #{params.inspect}
  MSG
}

With this in place, the CI logs themselves may contain enough information for you to figure out what went wrong.

How it works

The mechanism comes from the second argument (message) of RSpec::Expectations::ExpectationHelper.handle_failure.

You can see the relevant code here:
https://github.com/rspec/rspec-expectations/blob/v3.13.3/lib/rspec/expectations/handler.rb#L32-L34

What about .not_to and raise_error?

This technique works with .not_to and even expect { ... }.to raise_error.
That’s because all RSpec matchers eventually go through RSpec::Expectations::ExpectationHelper.handle_failure.

Conclusion

By passing a second argument or a block to RSpec’s .to method, you can fully customize failure messages.
This isn’t just about changing the wording—it’s also a powerful way to leave debugging information in CI logs, especially for flaky tests that are hard to reproduce locally.

The next time you encounter a hard-to-track bug, try this technique—you might get much closer to the root cause.


This content originally appeared on DEV Community and was authored by Takashi SAKAGUCHI