Avoid using SecureRandom when testing your Ruby code

SecureRandom is a great library for creating cryptographically secure random values in Ruby, and you should definitely use it in your application code where appropriate.

However, using SecureRandom when writing tests for your Ruby code can make your tests harder to debug and more prone to flakiness.

The Importance of Deterministic Tests

Deterministic tests are tests that produce consistent results each time they are executed, given the same input. You might have noticed that testing libraries like RSpec and MiniTest allow you to run your tests with a given seed value in order to reproduce errors or flaky failures.

For example:

$ rspec spec --seed 1234

Passing in this seed value is helpful when debugging tests that only fail sometimes because it allows you to set up your test environment to be almost identical to how it was when that test last failed. Behind the scenes, this sets Kernel::srand to the given seed value, making the output of the test deterministic.

Deterministic tests allow you to reproduce and investigate issues consistently. When a test fails, having a predictable outcome enables easier debugging and troubleshooting.

Deterministic tests also make it easier to collaborate with other developers. Since the tests generate consistent results, team members can share and compare their findings accurately across different development environments. This is particularly helpful if the failing tests occur as part of a Continuous Deployment test run because you can recreate the failures locally and fix them more easily.

Why Avoid SecureRandom in Tests

While SecureRandom is a great tool for generating cryptographically secure random values, it may not be suitable for use within tests due to its non-deterministic nature. Here’s why…

SecureRandom relies on unpredictable system-specific data to generate random numbers. This means that the output of SecureRandom is still different each time it’s called, even when seeding Kernel with a given seed. As a result, using SecureRandom within tests makes it difficult to reproduce the exact sequence of random numbers. In other words, using SecureRandom makes tests non-deterministic and thus harder to debug.

What’s the Alternative? Use Random

Instead of using SecureRandom, we can use the Random class in Ruby in tests and in factories. By providing a seed, we can ensure that Random generates the same sequence of random values for subsequent test runs, making the tests deterministic.

# BAD
it 'persists the Order' do
  order = Order.new(number: SecureRandom.alphanumeric(12))

  order.save

  expect(order).to be_persisted
end


# GOOD
it 'persists the Order' do
  order = Order.new(number: Random.alphanumeric(12))

  order.save

  expect(order).to be_persisted
end

Try It Yourself

To demonstrate the differences between Random and SecureRandom in a testing scenario, consider the following example written in RSpec. The code will print the values of Random and SecureRandom to STDOUT, allowing readers to observe the effects of seeding:

# spec_helper
RSpec.configure do |config|
  Kernel.srand config.seed
end

# test_file.rb
require 'spec_helper'
require 'securerandom'

RSpec.describe "Random" do
  describe '#uuid' do
    it "puts the same UUID each time" do
      puts "Random: #{Random.uuid}"
      expect(true).to be_truthy


    end
  end
end

RSpec.describe "SecureRandom" do
  describe '#uuid' do
    it "puts a different UUID each time" do
      puts "SecureRandom: #{SecureRandom.uuid}"
      expect(true).to be_truthy
    end
  end
end

Try running this a few times and comparing the printed values…

$ rspec test_file.rb --seed 1234

Written by

Photo of Gavin Morrice
Gavin Morrice

Software engineer based in Scotland

Connect with me