Keeping RSpec examples as atomic and lean as possible is a good rule of thumb. Nevertheless, sometimes it makes sense to combine multiple expectations into one test case - for example when the evaluation of the expression you want to test is “expensive” in some sense.

Let us study the Ruby class Pirate and add some specs for it:

class Pirate attr_accessor :mood def initialize @mood = 10 end def insult ( other_pirate ) self . mood += 1 other_pirate . mood -= 1 end end

When a pirate instance is insulting another pirate, this is affecting the fighter’s moods:

guybrush , le_chuck = Pirate . new , Pirate . new guybrush . mood # => 10 le_chuck . mood # => 10 guybrush . insult ( le_chuck ) guybrush . mood # => 11 le_chuck . mood # => 9

Let us turn this into proper test cases with RSpec. One way of expressing this is writing down two seperate test cases:

RSpec . describe Pirate do let ( :guybrush ) { Pirate . new } let ( :le_chuck ) { Pirate . new } it { expect { guybrush . insult ( le_chuck ) }. to change { le_chuck . mood }. by ( - 1 ) } it { expect { guybrush . insult ( le_chuck ) }. to change { guybrush . mood }. by ( 1 ) } end

But now let’s assume we really want to combine this into a single example where the expression guybrush.insult(le_chuck) is only evaluated once. One way to combine the expectations is wrapping one test into another, like the composition of functions in mathematics:

RSpec . describe Pirate do let ( :guybrush ) { Pirate . new } let ( :le_chuck ) { Pirate . new } it "affects the fighting pirate's moods" do expect { expect { guybrush . insult ( le_chuck ) }. to change { le_chuck . mood }. by ( - 1 ) }. to change { guybrush . mood }. by ( 1 ) end end

Unfortunately the probability is high that this strategy results in messy and unreadable test code as soon as you combine 3 or more expectations.

Better (imho) is using the neat method and that RSpec provides:

RSpec . describe Pirate do let ( :guybrush ) { Pirate . new } let ( :le_chuck ) { Pirate . new } it "affects the fighting pirate's moods" do expect { guybrush . insult le_chuck } . to change { le_chuck . mood }. by ( - 1 ) . and change { guybrush . mood }. by ( 1 ) end end

This is really elegant, but problems arise once you want to include tests for the fact that the corresponding expression guybrush.insult(le_chuck) does not affect some other object.

This is a priori not possible with the plain and -method above. But again RSpec has a really nice built-in solution for this problem: Definition of custom negated matchers! It’s pretty simple:

# This could live in your spec_helper.rb or wherever you configure RSpec RSpec :: Matchers . define_negated_matcher :not_change , :change

Afterwards you can happily use the operator not_change , the negated operator to change as we have defined above:

RSpec . describe Pirate do let ( :guybrush ) { Pirate . new } let ( :le_chuck ) { Pirate . new } let ( :otis ) { Pirate . new } let ( :carla ) { Pirate . new } it "affects the fighting pirate's moods" do expect { guybrush . insult le_chuck } . to change { le_chuck . mood }. by ( - 1 ) . and change { guybrush . mood }. by ( 1 ) . and not_change { otis . mood } . and not_change { carla . mood } end end

Give credit where credit is due: I found this solution within a StackOverflow answer. Thanks to the author!