DEV Community

Takashi SAKAGUCHI
Takashi SAKAGUCHI

Posted on

Tame Your Flaky RSpec Tests by Fixing the Seed

About This Article

Have you ever experienced tests that occasionally fail when running your RSpec test suite on CI? These are what we call flaky tests.

Simply re-running the test until it passes and calling it a day is a missed opportunity. Let’s take a more sustainable approach to fixing them.

Enable config.order and Kernel.srand

The key idea here is simple: enable the following configuration in your RSpec settings file, which is generated by default but commented out.

# spec/spec_helper.rb # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = :random # Seed global randomization in this process using the `--seed` CLI option. # Setting this allows you to use `--seed` to deterministically reproduce # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. Kernel.srand config.seed =end 
Enter fullscreen mode Exit fullscreen mode

Once enabled, this lets you reproduce random test failures deterministically using the seed printed after each test run.

These lines are typically wrapped in =begin and =end comments when generated, so make sure to move them out to enable them.

Reference:
https://github.com/rspec/rspec-core/blob/v3.13.2/lib/rspec/core/project_initializer/spec/spec_helper.rb#L86-L97

=begin These lines # are all comments =end 
Enter fullscreen mode Exit fullscreen mode

A Hands-On Example of Dealing with Flaky Tests

Let’s walk through an example to see how flaky tests can be identified and fixed.

Setup from rails new

# Create a new Rails app $ rails new --skip-test rspec-rails-test $ cd rspec-rails-test/  # Add and set up rspec-rails $ bundle add rspec-rails --group development,test $ rails generate rspec:install 
Enter fullscreen mode Exit fullscreen mode

Now activate the config.order and Kernel.srand settings by moving them outside of the comment block.

 config.profile_examples = 10 +=end   config.order = :random Kernel.srand config.seed -=end  end 
Enter fullscreen mode Exit fullscreen mode

Confirm RSpec runs successfully with zero examples:

$ bundle exec rspec No examples found. Randomized with seed 37597 
Enter fullscreen mode Exit fullscreen mode

Create a Model and Write a Test

Let’s create a simple model named YearMonth with year and month attributes, and some basic validations.

# app/models/year_month.rb class YearMonth include ActiveModel::Model include ActiveModel::Attributes attribute :year, :integer attribute :month, :integer validates :year, numericality: { only_integer: true } validates :month, numericality: { only_integer: true, in: (1..12) } end 
Enter fullscreen mode Exit fullscreen mode

Now, add a basic test to confirm that the model is valid.

# spec/models/year_month_spec.rb require "rails_helper" RSpec.describe YearMonth do subject { YearMonth.new(attributes) } let(:attributes) { { year:, month: } } let(:year) { rand(2100) } let(:month) { rand(12) } it do expect(subject.valid?).to be_truthy end end 
Enter fullscreen mode Exit fullscreen mode

Run the Test

$ bundle exec rspec Randomized with seed 54242 .  Finished in 0.0191 seconds 1 example, 0 failures 
Enter fullscreen mode Exit fullscreen mode

If you were unlucky and saw a failure like this, just re-run the test and confirm it eventually passes:

Randomized with seed 30925 F Failures: 1) YearMonth is expected to be truthy Failure/Error: expect(subject.valid?).to be_truthy expected: truthy value got: false 
Enter fullscreen mode Exit fullscreen mode

Observe Occasional Failures

Actually, this test sometimes fails.
After several runs, you should see an abnormal termination as shown below.

It is like a CI that is terminating normally and cheerfully in the product code, but sometimes notifies you of a failure.

$ bundle exec rspec  Randomized with seed 30925 F Failures: 1) YearMonth is expected to be truthy Failure/Error: expect(subject.valid?).to be_truthy expected: truthy value got: false  # ./spec/models/year_month_spec.rb:11:in 'block (2 levels) in <top (required)>'  Finished in 0.01313 seconds (files took 0.50544 seconds to load) 1 example, 1 failure Failed examples: rspec ./spec/models/year_month_spec.rb:10 # YearMonth is expected to be truthy  Randomized with seed 30925 $ 
Enter fullscreen mode Exit fullscreen mode

In real-world projects, it's useful to automate the test run in a loop and go grab lunch. Here’s a sample shell script:

#!/bin/bash set -euxo pipefail i=0 while true do i=$((i + 1)) bundle exec rspec done 
Enter fullscreen mode Exit fullscreen mode

Reproduce the Failure

The key detail is this line in the output:

Randomized with seed 30925 
Enter fullscreen mode Exit fullscreen mode

You can reproduce the exact test order and failure using this seed:

$ bundle exec rspec --seed 30925 
Enter fullscreen mode Exit fullscreen mode

Debug the Failure

Now that it’s reproducible, debugging is straightforward. Add a puts statement to inspect the attributes:

 it do + puts "attributes: #{subject.attributes.inspect}"  expect(subject.valid?).to be_truthy end 
Enter fullscreen mode Exit fullscreen mode
$ bundle exec rspec --seed 30925 attributes: {"year" => 1913, "month" => 0} F 
Enter fullscreen mode Exit fullscreen mode

The month is 0 — that’s not valid. The issue is with rand(12), which generates values from 0 to 11.

let(:month) { rand(12) } 
Enter fullscreen mode Exit fullscreen mode
> 10000.times.map { rand(12) }.uniq.sort => [0, 1, ..., 11] 
Enter fullscreen mode Exit fullscreen mode

Fix the Root Cause

Fix the range to 1..12 instead of 0..11:

- let(:month) { rand(12) } + let(:month) { rand(1..12) } 
Enter fullscreen mode Exit fullscreen mode
> 10000.times.map { rand(1..12) }.uniq.sort => [1, 2, ..., 12] 
Enter fullscreen mode Exit fullscreen mode

Now commit and push your fix:

git commit -m 'fix flaky test' 
Enter fullscreen mode Exit fullscreen mode

Summary

  • Enabling config.order = :random and Kernel.srand config.seed in spec/spec_helper.rb helps identify and debug flaky tests.
  • Use the printed seed value and --seed option to reliably reproduce failures.
  • Don’t leave flaky tests lying around — fix them to ensure your CI surfaces real issues, not noise.

Top comments (0)