What is Better Tests?

Better Tests is a collection of Javascript testing best practices inspired by BetterSpecs.org to improve your coding skills and level up your test suite.

Note: These recommendations use Jest syntax as the basis for testing Javascript. Make considerations when using a different framework.

Describe the object

Be clear about what function, class constructor, or component you are describing. It’s best to follow examples on MDN for how to describe Javascript objects and their usage. For example, the function parseInt() is represented with open parentheses and no parameters. Check the documentation for framework-specific examples.

Bad

describe('a function to transform data', () => {});

describe('a class to track analytics', () => {});

describe('a React component to render dates', () => {});

describe('a Vue component to render dates', () => {});

describe('a web component to render dates', () => {});

Good

describe('transformData()', () => {});

describe('Analytics()', () => {});

describe('<Dates/>', () => {}); 

describe('Vue.component('dates')', () => {});

describe('<dates>', () => {});
Edit this recommendation →

Nest describe blocks for context

Avoid nesting when testing user flows. Describe blocks are better at providing additional context.

Bad

describe('<ProductPage />', () => {
  it('displays the user as a guest when not logged in', () => {});

  it('prompts the user to login/signup in cart when not logged in', () => {});
  
  it('allows the user to proceed as a guest to checkout when not logged in', () => {});
});

Good

describe('<ProductPage/>', () => {
  describe('when user is a guest', () => {
    it('displays the user as a guest', () => {});

    it('prompts the user to login/signup in cart', () => {});
  
    it('allows the user to proceed as a guest', () => {});
  });
});
Edit this recommendation →

Describe expected behavior

Describe the expected behavior as clearly as possible, not the implementation details.

Bad

it('responds with a 302 if the user is not logged in', () => {});

it('transforms data correctly', () => {});

it('calls onSubmit when the user clicks the button', () => {});

Good

it('redirects guest users', () => {});

it('retreives column ids from columns', () => {});

it('submits the form', () => {});
Edit this recommendation →

Single expectation tests

Make only one assertion for each test. One test assertion helps find errors by displaying the exact failing test and makes your code readable. In isolated tests, you want each example to specify one, and only one, behavior. Multiple expectations result in unfocused and confusing tests.

Bad

it('redirects guests to the home page and prompts them to sign in', () => {
  ...
  expect(isUserLoggedIn).toBe(false);
  expect(isHomePage).toBe(true);
  expect(hasSigninPrompt).toBe(true);
});

Good

it('redirects guests to the home page', () => {
  ...
  expect(isHomepage).toBe(true);
});

it('redirects new accounts to the home page', () => {
  ...
  expect(hasSigninPrompt).toBe(true);
});
Edit this recommendation →

Don't use 'should'

“Should” is repetitive and doesn’t provide useful or meaningful information about expected behavior. Use actionable, present tense language in descriptions.

Bad

it('should sign up a user', () => {});

it('should prompt a guest to sign up', () => {});

it('should add to cart', () => {})

Good

it('signs up a user', () => {});

it('prompts a guest to sign up', () => {});

it('adds to cart', () => {})
Edit this recommendation →

Don't use mocks

Mocks are a code smell. Mocks are tempting but often test too much implementation and not enough behavior. Instead, find a way to stub the environment the way JSDOM stubs a browser DOM. You may want to use different tools in the future, and testing their implementation details will only stop you from refactoring.

Recomended libraries:

Bad

jest.mock('axios');

it('signs up a new user', () => {
  const {getByTestId} = render(<UserForm />);
  fireEvent.click(getByTestId('form-signup'));
  expect(axios.post).toHaveBeenCalledWith(
    'http://localhost.com/user/new',
    expect.anything()
  );
  expect(getByTestId('form-signup-success')).toBeTruthy();
});

Good

it('signs up a new user', () => {
  nock('http://localhost.com/user')
    .post('/new')
    .reply(200);

  const {getByTestId} = render(<UserForm onSubmit={onSubmit} />);
  fireEvent.click(getByTestId('form-signup'));
  expect(getByTestId('form-signup-success')).toBeTruthy();
});
Edit this recommendation →

Create only the data you need

It’s tempting to set up all the data required to avoid errors. However, this makes testing in isolation difficult and doesn’t allow you to address situations with missing data. Instead, add data for specific contexts or individual tests only when necessary.

Bad

describe('<AccountPage/>', () => {
  beforeEach(signInUser);
  beforeEach(generateOrderHistory);
  beforeEach(generateUserPreferences);
  ...
});

Good

describe('<AccountPage/>', () => {
  it('redirects users who are not signed in', () => {});

  describe('when a user is signed in', () => {
    beforeEach(signInUser);
    it('displays a message when the order history is empty', () => {});
    
    it('displays the order history', () => {
      generateOrderHistory();
      ...
    });
    
    it('sets the users preferences', () => {});

    it('displays the users previous preferences', () => {
      generateUserPreferences();
      ....
    });
  });
});
Edit this recommendation →