Blog

Thoughts from my daily grind

RSpec chain expect conditions with `and`

Posted by Ziyan Junaideen |Published: 18 April 2020 |Category: Ruby Language
Default Upload |

The and method was introduced to RSpec V2 and has been one of my favourite changes to the RSpec API. That change helped me make test code readable and avoid what I would call an "expect pyramid".

subject { described_class.perform(merchant, params) }

it 'authorizes a new transaction' do
  expect { subject }
    .to change(organization.donations, :count).by 1
    .and change(organization.authorisations, :count).by 1
    .and change(ActionMailer::Base.deliveries, :count).by 1
end

This is a significant improvement over what it used to be with Ruby 2.

it 'authorizes a new transaction' do
  expect do
    expect do
      expect do
        subject
      end.to change(organization.donations, :count).by 1
    end.to change(organization.authorizations, :count).by 1
  end.to change(ActionMailer::Base.deliveries, :count).by 1
end

The above example doesn't look bad, but I'd have to test changes of almost 8 models at once. That made the test code look taller than the pyramids at Giza.

The gotcha

The problem with this approach is that the chaining happens inside a to. If we need to assert that something does not change in the same chain, we will have to define a negate matcher.

RSpec::Matchers.define_negated_matcher :not_change, :change

Now you have an note_change matcher. Its not ideal, but it helps keep the test code clean.

it 're-authorizes the transaction' do
  expect { subject }
    .to not_change(organization.donations, :count)
    .and change(organization.authorisations, :count).by 1
    .and change(ActionMailer::Base.deliveries, :count).by 1
end
About the Author

Ziyan Junaideen -

Ziyan is an expert Ruby on Rails web developer with 8 years of experience specializing in SaaS applications. He spends his free time he writes blogs, drawing on his iPad, shoots photos.

Comments