Grouping RSpec Expectations with aggregate_failures for Cleaner Failure Reports
Learn how RSpec's aggregate_failures block groups multiple expectations into one failure report, reducing reruns and keeping specs focused on a single logical condition.
29 Sept 2026, 08:14 UTC

The problem: noisy failure output
When a single example contains several expect calls, RSpec stops at the first failure. You fix that one, rerun the suite, hit the next, and repeat. In a model validation spec that checks persistence, name format, and email syntax, this cycle can waste minutes per run.
How aggregate_failures works
aggregate_failures is a block helper introduced in RSpec 3.4. It collects every expectation failure inside the block and reports them together after the block finishes. The example still fails if any expectation fails, but you see all mismatches in one pass.
aggregate_failures do
expect(user).to be_persisted
expect(user.name).to eq('Ada')
expect(user.email).to match(URI::MailTo::EMAIL_REGEXP)
end
The block can appear in a regular example, a shared example, or a let‑defined subject. It does not alter pass/fail semantics—only the reporting.
When to use it (and when not to)
- Use it for tightly coupled assertions that describe a single conceptual condition: validating all fields of one record, checking a response object’s status, headers, and body together, or verifying a command’s side‑effects on a single aggregate.
- Avoid it when the expectations exercise independent behaviors. Separate examples keep failure attribution clear and preserve test isolation.
Worked example: validating a User model
Assume a User with name and email attributes. The spec below shows the difference in output.
Without aggregation
RSpec.describe User do
it 'is valid with proper attributes' do
user = User.create!(name: 'Ada', email: '[contact removed]')
expect(user).to be_persisted
expect(user.name).to eq('Ada')
expect(user.email).to match(URI::MailTo::EMAIL_REGEXP)
end
end
If the email is malformed, the run stops at the third expectation. You fix the email, rerun, then discover the name is wrong.
With aggregation
RSpec.describe User do
it 'is valid with proper attributes' do
user = User.create!(name: 'Ada', email: '[contact removed]')
aggregate_failures do
expect(user).to be_persisted
expect(user.name).to eq('Ada')
expect(user.email).to match(URI::MailTo::EMAIL_REGEXP)
end
end
end
Now a single run lists all three failures (if any) under the same example name, letting you correct them in one edit‑run cycle.
Trade‑offs and limits
| Aspect | Impact |
|---|---|
| RSpec version | Requires 3.4+; older versions raise NoMethodError. |
| Failure isolation | Grouping unrelated checks can obscure root cause. |
| Custom formatters | Some formatters or around hooks may render aggregated failures differently. |
| Test semantics | Unchanged – the example still fails if any expectation fails. |
Use the helper judiciously: keep the block focused on one logical assertion group.
Quick verification checklist
- Run
bundle exec rspec --versionand confirm ≥ 3.4. - Create a temporary spec with two deliberately failing expectations, once plain and once wrapped in
aggregate_failures. - Execute the spec and compare output: the aggregated version should list both failures under a single example name.
- Ensure the project uses the modern
expectsyntax (noshould).
If the aggregated output appears as expected, the feature is ready for adoption in the relevant specs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.