RSpec Negated Matchers

Engineering Insights

April 29, 2025
Taylor Kearns
#
Min Read
RSpec Negated Matchers

Problem

We want to test that when we call our Ruby method, one result does occur while another result does not occur. Specifically, we want to test that an Event is created but that an EventPayload is not created. Using RSpec, we have this so far:

it "creates an event without an associated payload" do
  expect { service.create_event! }.to change { Event.count }.by(1)
  expect { service.create_event! }.not_to change { EventPayload.count }
end

This is ok, but we'd like to run both expectations on a single method call.

We know that RSpec has and which can help us make compound expectations, but this falls apart when we want to make one positive assertion and one negative assertion. We could try this:

it "creates an event without an associated payload" do
  expect { service.create_event! }
    .not_to change { EventPayload.count }
    .and change { Event.count }.by(1)
end

But no joy:

NotImplementedError: expect(...).not_to matcher.and matcher` is not supported, since it creates a bit of an ambiguity.

Solution

How can we chain two expectations together with and if one is a "not" condition? RSpec allows us to define a negated matcher:

RSpec::Matchers.define_negated_matcher :not_change, :change

Which allows us to then write this:

it "creates an event without an associated payload" do
  expect { service.create_event! }
    .to change { Event.count }.by(1)
    .and(not_change { EventPayload.count })
end

And we can flip the order if we choose:

it "creates an event without an associated payload" do
  expect { service.create_event! }
    .to not_change { EventPayload.count }
    .and change { Event.count }.by(1)
end

Fun!

Learn more about how The Gnar builds Ruby on Rails applications.

Related Insights

See All Articles
Engineering Insights
React in Rails, One Component at a Time

React in Rails, One Component at a Time

It seems like we should be able to drop React components into our views on an ad hoc basis using Stimulus, which is exactly what we will do.
Engineering Insights
A Near Miss: How I Froze My Website by Adding a Generated Column

A Near Miss: How I Froze My Website by Adding a Generated Column

Do avoid freezing your database, don't create a generated column. Add a nullable column, a trigger to update its value, and a job to backfill old data.
Engineering Insights
The Programmer's Obsession: Lessons from Monica Geller's Light Switch

The Programmer's Obsession: Lessons from Monica Geller's Light Switch

If you're wondering if you'd make a good software engineer, ask yourself how much you're like Monica from Friends.
Previous
Next
See All Articles