Refactor away duplication in your RSpec tests using the Parameterized Test Method

What do you do when you need to run the same test multiple times, but with different parameters? If you copy and paste the test, you end up with a hard-to-read test file. You can’t easily tell how the tests differ from one another. Worse, when you need to change one, you need to change them all. Take the following simple test file:

def double_it(number)
  number * 2
end

describe '#double_it' do
  it 'doubles 1 into 2' do
    expect(double_it(1)).to eq(2)
  end

  it 'doubles 2 into 4' do
    expect(double_it(2)).to eq(4)
  end

  it 'doubles 4 into 8' do
    expect(double_it(4)).to eq(8)
  end
end

There are a few ways we could reduce the duplication here. My favorite is to use a Parameterized Test Method.

Reducing duplication with a Parameterized Test Method

Parameterized Test Method is a fancy-sounding name for a simple idea: a method, that takes parameters, and generates a test. Here’s how you use it:

  1. Take one of the tests and wrap it in a method. Then call the method in place of the test.
  2. Extract the unique test data to method arguments, and pass them in via the method call.
  3. Replace the remaining tests with calls to the new test method.

I’ll walk you through the refactoring, step-by-step:

1. Wrap the first example in a method and call it

describe '#double_it' do
  def self.test_double_it
    it 'doubles 1 into 2' do
      expect(double_it(1)).to eq(2)
    end
  end

  test_double_it

  it 'doubles 2 into 4' do
    expect(double_it(2)).to eq(4)
  end

  it 'doubles 4 into 8' do
    expect(double_it(4)).to eq(8)
  end
end

2. Extract the unique test data to method arguments

describe '#double_it' do
  def self.test_double_it(initial, doubled)
    it "doubles #{initial} into #{doubled}" do
      expect(double_it(initial)).to eq(doubled)
    end
  end

  test_double_it 1, 2

  it 'doubles 2 into 4' do
    expect(double_it(2)).to eq(4)
  end

  it 'doubles 4 into 8' do
    expect(double_it(4)).to eq(8)
  end
end

3. Replace the remaining tests with calls to the new test method

describe '#double_it' do
  def self.test_double_it(initial, doubled)
    it "doubles #{initial} into #{doubled}" do
      expect(double_it(initial)).to eq(doubled)
    end
  end

  test_double_it 1, 2
  test_double_it 2, 4
  test_double_it 4, 8
end

That’s it!

Why use the Parameterized Test Method

You might wonder why I didn’t just iterate over the expectation, or even over the entire it block. There are a few reasons:

  1. It’s more readable – I don’t like to use iteration in my tests, I find it complex and less readable when setting up tests.
  2. Documentation – you get the exact same documentation format as before, with one line of documentation per example.
  3. Test failures are easier to understand – when a test fails, you know the exact test and line number that it failed on.

The next time you find yourself writing the same test over and over again, try refactoring the duplication away using a parameterized test method!

Are you stuck as a junior developer... forever?

You don't know what to study to level up your skills. Lose this job, and you're screwed.

How does an extra $80k per year sound? Choose the kind of job you want.

Enter your name and email below and I’ll show you how to level up to Senior Developer...