
Keeping a Rails Test Suite Fast and Reliable
Every Rails team starts with a quick test suite. Two years later it takes 25 minutes, a few tests fail randomly, and developers push without running it. At that point the suite is costing more than it saves.
Here is how we bring Rails suites back to fast and trustworthy, and keep them there.
Measure Before You Optimize
Find out where the time goes. Both Minitest and RSpec can report the slowest tests:
# RSpec
bundle exec rspec --profile 20
# Minitest (with minitest-reporters or Rails 7.1+)
bin/rails test --profile 20
Usually a small number of files (often system tests and a few heavy model specs) account for most of the runtime. Fix those first.
Create Less Data
The most common slowdown is factories that build far more records than the test needs:
# One invoice quietly creates a customer, an account, a plan, and five line items
let(:invoice) { create(:invoice) }
# Often enough
let(:invoice) { build_stubbed(:invoice) }
- Prefer
buildorbuild_stubbedwhen the test does not hit the database - Keep default factories minimal and use traits for the extras
- Consider fixtures for stable reference data: they load once per run and are very fast
Run Tests in Parallel
Rails has built-in parallel testing. On a modern laptop this alone can cut runtime by more than half:
# test/test_helper.rb
class ActiveSupport::TestCase
parallelize(workers: :number_of_processors)
end
Each worker gets its own database. Tests that depend on global state, shared files, or fixed ports will start failing, which is useful: those tests were fragile already.
Keep System Tests for Critical Paths
Browser-driven system tests are slow and the most likely to flake. Use them for the flows that make money or lose trust if they break: sign up, checkout, core CRUD screens. Cover edge cases in model, request, and component tests instead.
When you do write system tests:
- Use Capybara matchers that wait (
assert_text,have_content) instead ofsleep - Assert on what the user sees, not on CSS details that change often
- Run headless Chrome in CI with a fixed window size
Hunt Down Flaky Tests
A flaky test teaches the team to ignore red builds. Common causes in Rails apps:
- Time - use
travel_toinstead of relying on the real clock - Order - run with a random seed and reproduce failures with the same seed
- External calls - stub HTTP with WebMock or VCR so tests never hit the network
- Async jobs - use the test adapter and perform jobs explicitly
test "trial ends after 14 days" do
travel_to Time.zone.local(2026, 8, 1) do
account = accounts(:trial)
travel 15.days
assert account.reload.trial_expired?
end
end
Quarantine a flaky test with a ticket and an owner, then fix it within days. Do not let it live forever behind a retry.
Make CI Do the Heavy Lifting
- Cache gems and node modules between runs
- Split the suite across several CI machines by timing data
- Run linters and fast unit tests first, system tests last, and fail fast
- Track total runtime over time, and treat a big jump like a performance regression
Conclusion
A fast, reliable suite changes how a team works: smaller commits, braver refactors, and confident deploys. Budget a little time each sprint for test health and it stays that way.
Is your Rails test suite slowing the team down? Talk to us about a test and CI tune-up.