Unit testing

Overview

Our unit tests are written with Jest and, for React components, React Testing Library. They run in Node with a simulated DOM (jsdom), so they do not need a Firefox build. They cover both content code (React components, etc.) and .sys.mjss.

You can find unit tests in test/jest.

Execution

To run lint and the unit tests once, execute npm test. To run only the unit tests, use npm run testmc:jest, and add -- path/to/file.test.jsx to run a single file.

To run unit tests continuously (i.e. in “test-driven development” mode), run npm run tdd.

To generate a coverage report in logs/coverage, run npm run testmc:jest:coverage, then npm run debugcoverage to open it.

Where to put new tests

Add new tests to the subdirectory of test/jest that corresponds to the file you are testing. Tests should end with .test.js, or .test.jsx if the test includes any JSX.

For example, if the file you are testing is lib/Foo.sys.mjs, the test file should be test/jest/lib/Foo.test.js.

Writing tests

Jest provides describe, it, beforeEach, expect, and the rest as globals. Use jest.fn() and jest.spyOn() for stubs and spies, and jest.useFakeTimers() for timers.

describe("FooModule", () => {
  it("should eventually get the meaning of life", async () => {
    const foo = new FooModule();
    expect(await foo.meaningOfLife()).toBe(42);
  });
});

Overriding globals in .sys.mjss

Most .sys.mjss read globals such as Services or lazily imported modules. To replace them for a test, use stubGlobals from test/jest/test-utils. It returns a function that restores the originals:

import { stubGlobals } from "test/jest/test-utils";

describe("MyModule", () => {
  let restoreGlobals;
  beforeEach(() => {
    restoreGlobals = stubGlobals({ AboutNewTab: { override: jest.fn() } });
  });
  afterEach(() => restoreGlobals());
});

mockServices(["obs", "prefs"]) builds a Services-shaped object of stubs to pass to stubGlobals.

Testing React components

Render components with React Testing Library and query them the way a user would find them. Wrap connected components in WrapWithProvider from test/jest/test-utils, which supplies a Redux store built from INITIAL_STATE (or a state you pass in):

import { render, screen } from "@testing-library/react";
import { WrapWithProvider } from "test/jest/test-utils";

it("should render the heading", () => {
  render(
    <WrapWithProvider>
      <Foo />
    </WrapWithProvider>
  );
  expect(screen.getByRole("heading")).toBeInTheDocument();
});