Today I Learned

Aggregating failures in RSpec

Sometimes our examples have multiple independent expectations. In such cases, the default behavior of RSpec to abort on the first failure may not be ideal.

Consider the following example:

it do
  get('/api/v1/users')

  expect(response.status).to eq(200)
  expect(response.body).to eq('[{"name":"Johny"}]')
end

If our API returns a wrong status, RSpec will print the following output:

1) Users GET /api/v1/users example at ./spec/requests/api/v1/users_spec.rb:9
   Failure/Error: expect(response.status).to eq(200)
   
     expected: 200
          got: 201

While this gives us feedback on the response’s status being wrong, it entirely skips the assertion on the response’s body, even though having both results could make debugging easier.

RSpec has a neat solution to this: Aggregating Failures.
To use it, you can either tag the whole example with :aggregate_failures:

it 'does something', :aggregate_failures do
  ...
end

Or you can just wrap your assertions in an aggregate_failures block:

it do
  get('/api/v1/users')

  aggregate_failures do
    expect(response.status).to eq(200)
    expect(response.body).to eq('[{"name":"Johny"}]')
  end
end

This will change RSpec’s default behavior and will group both expectations:

1) Users GET /api/v1/users example at ./spec/requests/api/v1/users_spec.rb:9
   Got 2 failures from failure aggregation block.

   1.1) Failure/Error: expect(response.status).to eq(200)
        
          expected: 200
               got: 201

   1.2) Failure/Error: expect(response.body).to eq('[{"name":"Johny"}]')
        
          expected: "[{\"name\":\"Johny\"}]"
               got: "[{\"name\":\"Jane\"}]"

More info: documentation