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.