A Live Developer Journal

Remove Setup Duplication From Rspec Tests

What do we have?

We use the same setup code for an object across multiple test examples, and want to remove duplication so that we don’t have to keep repeating ourselves.

Example:

describe Object should do

it do something do
object = Object.new # setup
  object.do_something
  expect(object.actual).to eq(expected)
expect(object.actual).to eq(expected)
  end

  it do another thing do
  object = Object.new # setup
#behaviour & assertions
  end

  it do that thing do
  object = Object.new # setup
# behaviour & assertions
  end
  end

What can we do about it?

Option 1: Do nothing

Ask yourself if removing setup code duplication is going to make your life easier. Will it make the code more readable or easier to maintain? Then that’s a good reason to do it.

Otherwise, we run the risk of having to bounce around in our codebase from the examples to the setup code in order to understand what’s going on. Chances are it’s better that you DO do it, but something to think about.

Option 2: Use a before hook

A before hook lets us run a chunk of code before running every test example. We can setup our object (object.new and configurations) inside that, and each example we run will have it’s own version of that object.

  describe "Object should" do

# multiline block
  before do
  @object_one = ObjectOne.new
  @object_two = ObjectTwo.new
  end

# or singleline block
  before { @object = Object.new }

  it "do something" do
# behaviour and assertions
  end

  end

Advantages

Disadvantages

Option 3: Use a helper method

RSpec is just Ruby under the covers, which means that we can define our own helper methods as if we were writing methods inside of our custom object.

The only implementation difference between this approach and the before method is that we are storing our object setup code inside of a method, instead of a hook.

However, in the before method, we are storing the object setup code in an instance variable, which means that it is created only the once per test example.

Whereas in the helper method, we are creating the object every time we call the helper method, not just once at the start (the object is stored in the instance variable until it is overwritten by the next call). This is fine if we don’t want to write assertions based on a chain of previous behaviours, but is a problem if we do.

An alternative helper method approach is to use a technique called memoization, which is where we only create the object if it hasn’t already been created (if the value stored in the instance variable from the last call is nil or false), using the |= assignment operator.

Both techniques are demonstrated below:

  describe "Object should" do

  def object
# new object on every call
  @object = Object.new
# or new object if not already created
  @object |= Object.new
  end

  it do something do
  object.do_something
expect(object.actual).to eq(expected)
  end

# ...
  end

Advantages

Disadvantages

Option 4: Use a let declaration

Let declarations is an RSpec specific construct which lets us handle nil edge cases (see disadvantages of the helper method approach). It works the same as the memoization helper method technique, except it creates and stores the object the first time it’s called, whether or not the value is nil.

  describe "Object should" do

  let(:object) { Object.new() }

  it do something do
  object.do_something
expect(object.actual).to eq(expected)
  end

# ...
  end

Advantages

Disadvantages

Option etc: Any Ideas?

If you know of additional techniques that haven’t been covered above, let us know down in the comments, please and thank you.