Skip to content

TDD How To

Stephen Cresswell edited this page Apr 14, 2019 · 3 revisions

The classic way to do TDD is

  1. Write a test
  2. Watch it fail
  3. Fix the test
  4. Refactor

But there's more to the technique than just the above...

  1. Start by writing an assertion
    it('should retrieve customer by email', async () => {
      assert.equal(name, "Steve");
    })
    In a statically typed language the above code will fail to compile, because name is not defined. Use your IDE to auto-fix the code until it compiles. As soon as the test compiles...
  2. Run the test
  3. Grow the test from bottom up, adding / auto-fixing the code that will make the test pass.
    it('should retrieve customer by email', async () => {
      const name = customer.name;
      assert.equal(name, "Steve");
    })
    it('should retrieve customer by email', async () => {
      const customer = await store.findByEmail('steve@example.com');
      const name = customer.name;
      assert.equal(name, "Steve");
    });
    it('should retrieve customer by email', async () => {
      const store = new CustomerStore();
      const customer = await store.findByEmail('steve@example.com');
      const name = customer.name;
      assert.equal(name, "Steve");
    });
    it('should retrieve customer by email', async () => {
      const store = new CustomerStore();
      await store.save({ name: 'Steve', email: 'steve@example.com');
      const customer = await store.findByEmail('steve@example.com');
      const name = customer.name;
      assert.equal(name, "Steve");
    });

Assertion Libraries

The key criteria when choosing an assertion library, is to maximise the signal to noise ratio - the less boilerplate the better. I consider the trend.of.fluid.assertion.apis to be an anti-pattern.

const { equal } = assert;

equal(name, 'Steve');

Carries a higher signal to noise ratio

expect(name).to.be.equal('Steve');

There are more TDD Anti Patterns here

Test side-effects, not interactions

It's also common to use a test double (e.g. a mock) when writing unit tests, but this is yet another anti-pattern. The problem with mocks (and also spies) is that they assert interactions. i.e. that function "save" was called with parameters "Steve" and "steve@example.com". This leads to brittle tests. If I reverse the order of the parameters in all production code, my test will still fail. Perhaps worse is that test doesn't actually provide documentation on the desired behaviour.

Instead test for side effects. In the above case, what's important that the record is correctly persisted, than the order of the arguments, so this is what we should be testing for.

We still need to test code in isolation though, but instead of using mocks and spies, it's better to use stubs (e.g. an in-memory store), that can be interrogated for the side effects.

Clone this wiki locally