JS Unit Testing

JavaScript Unit Testing

We use Jest as our JavaScript unit testing framework.

All JavaScript written on the global web platform (and in Fozzie modules) must have associated unit tests. We aim for 100% coverage, with this checked by coveralls as part of our PR review workflow.

Folder structure

The tests for each directory can be found in the tests folder.

src/
├── js/
│   └── tests/

Running the tests

The JavaScript unit tests can be run by typing the following from the folder containing the module’s package.json:

yarn test

This will run the gulp-build-fozzie scripts:test task which runs any unit tests found in the JavaScript source directory using Jest.

Code coverage

To run the tests and see code coverage run:

yarn test:cover

This will run the tests and display a report once finished showing source files with coverage stats.

Writing tests

We use Jest to write our unit tests, which uses jsdom behind the scenes to provide DOM functionality.

A basic test file looks like this:

it('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

You can also use describe() to create a block which groups together several related tests in one "test suite", this can be handy if you prefer your tests to be organized into groups.

Note that when using describe to wrap tests together, the full statement formed from both the describe() and it() descriptions should read correctly as a full sentence.

describe('the sum function', () => {
  it('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2)).toBe(3);
  });
});

Take a look at the Jest docs for the full list of globals and expectations available.

Testing modules

When testing modules, import the module you wish to test, then write your tests against the imported functions or variables:

import MegaModal from 'MegaModal';

it('instance should be correct type', () => {
    // Act
    const modal = new MegaModal();

    // Assert
    expect(modal instanceof MegaModal).toBe(true);
});

Testing the browser DOM

Our test setup makes use of jsdom, which allows us to test code which uses DOM methods. For example:

import { showLoader } from 'loader';

it('loader class is added to element', () => {
    // Arrange
    const element = document.querySelector('body');

    // Act
    showLoader(element);

    // Assert
    expect(element.classList.contains('is-loading')).toBe(true);
});

In this example we use document.querySelector() to select the body element of the document, and later use element.classList to verify that a class is present. Both of these methods are part of the DOM.

Testing code with specific page HTML

Set the HTML of the webpage using document.body.innerHTML for a single test.

import { checkInputByValue } from 'checkboxHelper';

it('can check correct checkbox', () => {
    // Arrange
    const value = 'test';
    document.body.innerHTML = `<input type="checkbox" value="${value}" />`;

    // Act
    const result = checkInputByValue(value);

    // Assert
    expect(document.querySelector('input').checked).toBe(true);
});

Now we can verify, using DOM methods, that the expected actions were carried out inside the code under test.

Arrange, Act, Assert

Tests should be split into three parts, using the AAA pattern. This keeps the structure of the tests consistent across all test files.

Right now, if you open up most of the test files, you'll notice comments inside the tests which explicitly state each section (arrange, act, assert), this is to help you to easily parse and understand what is happening inside the test, and also for newcomers to the tests so that they are aware that there is a pattern in use here and they should also follow this convention.

Arrange

Declare any variables which are going to used inside the test e.g. parameters for functions, expected results, etc.

Act

Invoke the unit under test, usually a function call.

Assert

Verify that the results are as expected.

Stupidly simple tests

The tests should be very easy to read and understand. They should also be very short and focused.

Because jsdom is used to create an in-memory representation of the DOM, we avoid the need for most of the mocking seen in the older tests. This helps us achieve the goal of keeping tests short and focused.

See this guide on how to write great JavaScript unit tests for some principles and guidelines you should keep in mind when writing your unit tests.