BRIEFS logo BRIEFS

Nested Unit Tests: An Anti-Pattern

05/25/2016 , 2m, 58s

At first I was really bothered that AVA didn't have support for nested tests, but now I consider nesting tests to be an anti-pattern.

Here's an example of the kind of thing I mean (see this in a syntax-highlighted gist here):

Mocha with nesting (don't mind the actual tests, just imagine what this would be like with a larger test file):

import Customers from './Customers'
import getMockCustomers from './Customers/mocks'
describe('Customers', () => {

  let mockCustomers

  beforeEach(() => {
    Customers.setCustomers([]) // initialize to empty for most tests
    mockCustomers = getMockCustomers() // have mock customers available
  })

  afterEach(() => {
    Customers.setCustomers([]) // clean up just in case
  })

  describe('getCustomers', () => {
    beforeEach(() => {
      Customers.setCustomers(mockCustomers)
    })

    it('should return the existing customers', () => {
      const customers = Customers.getCustomers()
      expect(customers).to.be.eql(mockCustomers)
      // questions you must ask if you're not familiar with this file and
it's bigger:
      // - where does `mockCustomers` come from?
      // - what make `getCustomers` return the mockCustomers?
    })
  })

  describe('setCustomers', () => {
    it('should set the customers array', () => {
      Customers.setCustomers(mockCustomers)
      const customers = Customers.getCustomers()
      expect(customers).to.be.eql(mockCustomers)
      // question you must ask if you're not familiar with this file and
it's bigger:
      // - where does `mockCustomers` come from? What is it initialized as?
    })
  })
})

Here's what it would look like without nesting (with AVA, though you could mostly do this with Mocha too):

import test from 'ava'

test('getCustomers: should return the existing customers', t => {
  const mockCustomers = initializeCustomers()
  const customers = Customers.getCustomers()
  t.deepEqual(customers, mockCustomers)
  cleanupCustomers()
})

test('setCustomers: should set the customers array', t => {
  initializeCustomers([])
  const mockCustomers = getMockCustomers()
  Customers.setCustomers(mockCustomers)
  const customers = Customers.getCustomers()
  t.deepEqual(customers, mockCustomers)
  cleanupCustomers()
})

function initializeCustomers(initialCustomers = getMockCustomers()) {
  Customers.setCustomers(initialCustomers)
  return initialCustomers
}

function cleanupCustomers() {
  Customers.setCustomers([])
}

Like I said, this may be a bad example. Don't be distracted by the tests themselves. Just look at the it blocks above in the Mocha tests and compare it to the test block in the AVA tests below. The difference is that with the it blocks, you have to keep in your head what's going on in a beforeEach and that relationship is implicit. In the test block, there is a level of abstraction, but it's explicit so you don't need to worry about keeping it in your head because it's in front of your face.

I hope this is helpful!