How much duplication should we tolerate in tests?

As a developer I’m used to avoiding duplication in code. Avoiding duplication is even listed as one of the 4 rules of the TDD cycle:

  1. Start with a failing automated test
  2. You cant add new code without corresponding automated test
  3. You cant add tests if you have a red bar
  4. Eliminate duplication

Yet quite often there is very obvious duplication in our code that we are willing to accept – within the test code itself.

Here’s an example of the code you might write to test the attributes of a new class:

describe User do
  it 'should respond to name' do respond_to(:name)

  it 'should respond to email' do respond_to(:email)

  it 'should respond to age' do respond_to(:age)

  it 'should respond to gender' do respond_to(:gender)

And here is the class under test:

class User
  attr_accessor :name, :email, :age, :gender

This is a very simple example, but already we see a whole bunch of duplication within the test code. Every test creates a new user as well as calling the respond_to matcher.

You could argue that this type of duplication in test code is perfectly acceptable, but let’s put aside that argument for a second and look at possible ways to remove this duplication.

Using let and subject

The easiest bit of duplication is the call. We could use a before block to instantiate a user field, but a let statement would probably work best here.

describe User do
  let(:user) { }

  it 'should respond to name' do
    user.should respond_to(:name)

  it 'should respond to email' do
    user.should respond_to(:email)

  it 'should respond to age' do
    user.should respond_to(:age)

  it 'should respond to gender' do
    user.should respond_to(:gender)

That’s slightly better – we have removed the duplication around instantiating the user object. We can do even better by setting the user as the subject:

describe User do
  subject { }

  it { should respond_to(:name) }
  it { should respond_to(:email) }
  it { should respond_to(:age) }
  it { should respond_to(:gender) }

Which is much better! If you’re not familiar with subject syntax I’m using here – it’s a really useful way of cleaning up your tests. You can either define an explicit subject block (which is what I have done here) or use the implicit subject, which is an instance of the target of the describe block. (So in the example I gave here the explicit subject block is superfluous – I can take it out and the test will work exactly the same). You can now test either the subject itself, or test attributes on the subject.

its(:email) { should be_nil }

If you are interested in learning more about the subject syntax, look here.

Using an each block

The subject syntax I used above can work really well in certain scenarios, but it can also get quite messy – especially when the setup of the object state is complicated.

So another way of removing the duplication we saw above would be to use a simple each block:

describe User do
  [:name, :email, :age, :gender].each do |field|
    it "should respond to #{field}" do respond_to(field)

Again, we’ve removed the duplication and the tests are still straightforward and readable. This is a pattern I’ve used a few times to clean up tests when the subject syntax doesn’t really work that well.

How much duplication should we tolerate in tests

Let’s get back to the earlier argument – should we try to remove all duplication in tests? There is a similar discussion on StackOverflow: Is duplicated code more tolerable in unit tests?

I would argue that in certain cases readability should take presedence over removing duplication. There’s nothing worse than having to wade through some heavily refactored test code to figure out what exactly is failing. You especially don’t want to get to the point where your testing code is so complicated that you need to write tests for the tests.

In my opinion it’s usually a good idea to refactor the code for settting up the state – for example, extracting the setup code into a factory. On the other hand, it’s usually not such a good idea to refactor the code that actually exercises the code. If you stick to these general guidelines you should still end up with tests which are short and readable with a level of duplication that is possibly higher than implementation code but still acceptable.

As with so many things in programming (and testing) there is no clear answer and no silver bullet. Find what works for you and don’t be afraid to refactor.

Happy coding.