Flip your tests

Automated tests are great. They can help you run through hundreds of input combinations in a matter of seconds, a task that would be prohibitively burdensome to test by hand.

In my experience, a typical test suite looks like this:

describe('my test suite', () => {
  it('should work with basic test case', async () => {
    const user = await UserFactory.create({});
    expect(user.id).toBe(null);
    expect(user.name).toBe(null);
  });
  it('should work with a long name', async () => {
    const user = await UserFactory.create({
      firstName: 'Pippilotta',
      middleName: 'Delicatessa',
      lastName: 'Windowshade Mackrelmint Ephraimsdaughter Longstocking',
    });
    expect(user.id).toBe(null);
    expect(user.name).toBe('Pippilotta Delicatessa Windowshade Mackrelmint Ephraimsdaughter Longstocking');
  });
});

This design reflects the order in which an engineer has approached the problem. Often, the test cases directly correspond to edge cases that the engineer has considered. Each test follows this approximate format:

However, there are a few drawbacks to this style:

Here's an alternate design:

describe('my test suite', () => {
  describe('basic test case', () => {
    let user;
    beforeAll(async () => {
      user = await UserFactory.create({});
    });
    it('should set null user id', async () => {
      expect(user.id).toBe(null);
    });
    it('should set null user name', async () => {
      expect(user.name).toBe(null);
    });
  });
  describe('with a long name', () => {
    let user;
    beforeAll(async () => {
      user = await UserFactory.create({
        firstName: 'Pippilotta',
        middleName: 'Delicatessa',
        lastName: 'Windowshade Mackrelmint Ephraimsdaughter Longstocking',
      });
    });
    it('should set null user id', async () => {
      expect(user.id).toBe(null);
    });
    it('should correctly form full name', async () => {
      expect(user.name).toBe(
        'Pippilotta Delicatessa Windowshade Mackrelmint Ephraimsdaughter Longstocking'
      );
    });
  });
});

This has several advantages:

When you adopt a codebase, it's easy to fall into the patterns and conventions established by that codebase. With a little effort, though, you can start new habits that will reduce the amount of work you'll need to do in the future.