Unit Tests

To unit test your Lightning web components, use the Jest test framework in combination with the @lwc/jest packages.

You can install the @lwc/jest-preset package, which includes the base packages and configurations. Alternately, you can install and configure the @lwc/jest packages individually. Jest is easy to set up and comes with a lot of great features such as mocks, snapshot testing, and jsdom.

Install Jest

  1. To install Jest, run this command inside your package:

    npm install jest --save-dev
    
  2. To install @lwc/jest-preset, run this command inside your package:

    npm install @lwc/jest-preset --save-dev
    
  3. If you didn't install @lwc/jest-preset, run these commands inside your package to install the individual @lwc/jest packages:

    npm install @lwc/jest-transformer --save-dev
    npm install @lwc/jest-resolver --save-dev
    npm install @lwc/jest-serializer --save-dev
    

Configure Your Project to Use Jest

The easiest way to configure your project is to use the preset configurations provided by @lwc/jest-preset. Add this entry to the package.json of your project.

{
  "jest": {
    "preset": "@lwc/jest-preset"
  }
}

Next, update the Jest config entry for moduleNameMapper. This config tells Jest where your Lightning Web Components modules are located. If the directory structure in the following section is used, the moduleNameMapper entry looks like this:

{
  "jest": {
    "preset": "@lwc/jest-preset",
    "moduleNameMapper": {
        "^foo-(.+){{BODY}}quot;: "<rootDir>/src/test/modules/foo/$1/$1"
    }
  }

Run Tests

To run tests, add scripts to package.json. To automatically re-run tests related to files that have changed, add the --watch parameter.

{
  "scripts": {
    "test:unit": "jest",
    "test:unit:watch": "jest --watch"
  }
}

To run the scripts, use these commands.

npm run test:unit
npm run test:unit:watch

Component Directory Structure

Create a folder named __tests__ at the top level of your component’s bundle directory. Save all tests for this component inside the __tests__ directory, or in a subdirectory of the __tests__ directory.

Jest runs JavaScript files in the __tests__ directory. Test files must have names that end in “.js”, and we recommend that tests end in .test.js. You can have a single test file with all of your component tests, or you can have multiple files to organize related tests.

For example, if you had a component, todo-list, under the foo namespace, the project structure with tests looks like this.

src
├── modules
│   └── foo
│       └── todoList
│           ├── __tests__
│           │	      └── todoList.test.js
│           ├── todoList.html
│           └── todoList.js
└── main.js

Write a Basic Test

To become an accomplished tester, learn how to use Jest. In particular, learn the syntax of the many matchers provided with Jest. We don’t cover general Jest usage here because the Jest documentation is excellent. We focus on the specifics of using Jest with Lightning web components.

Jest tests for a Lightning web component should test the behavior of a single component in isolation, with minimal dependencies on external components or services.

Let’s look at hello.test.js, which is the test for the hello component in the lwc-recipes-oss repo.

// hello.test.js
import { createElement } from 'lwc';
import Hello from 'c/hello';

describe('example-hello', () => {
  afterEach(() => {
    // The jsdom instance is shared across test cases in a single file so reset the DOM
    while (document.body.firstChild) {
      document.body.removeChild(document.body.firstChild);
    }
  });

  it('displays greeting', () => {
    // Create element
    const element = createElement('example-hello', {
      is: Hello
    });
    document.body.appendChild(element);

    // Verify displayed greeting
    const div = element.shadowRoot.querySelector('div');
    expect(div.textContent).toBe('Hello, World!');
  });
});

Let’s walk through the code and learn about each section of the test file. All tests must use this structure.

Imports

First, the test imports the createElement method. This method is available only in tests. Here, lwc refers to the Lightning Web Components engine. @lwc-/jest-resolver resolves that import to point to the version of the engine that's imported in your project.

To import the component under test, use a relative path to the .js file or reference the component by namespace-name. If you import via the namespace-name method, your moduleNameMapper entry in the project's Jest config must correctly map to your modules directory.

In this example, the component under test is c/hello. Later, the code uses these imports to create the component under test.

import { createElement } from 'lwc';
import Hello from 'c/hello';

describe block

A describe block defines a test suite. A test suite contains one or more tests that belong together from a functional point of view.

describe('example-hello', () => {
    ...
});

We recommend having a top level describe block with a description matching the component name. Add more describe blocks that group functionality only if necessary.

For hello.test.js, a single describe is sufficient. For more complex components, it may make sense to have several describe blocks that group things into categories like error scenarios, empty input, wired data, regular data, and so on.

Cleanup

The Jest afterEach() method resets the DOM at the end of the test.

Since a browser isn’t running when tests run, Jest uses jsdom to provide an environment that behaves much like a browser’s DOM or document. Jest has a dependency on jsdom, which is a Node.js project, so jsdom is downloaded during installation of the lwc-jest project the same way Jest itself is.

Each test file shares a single instance of jsdom, and changes aren’t reset between tests inside the file. Therefore it’s a best practice to clean up between tests, so that a test’s output doesn’t affect any other test.

afterEach(() => {
    // The jsdom instance is shared across test cases in a single file so reset the DOM
    while (document.body.firstChild) {
        document.body.removeChild(document.body.firstChild);
    }
});

Jest also has other methods that you can use to perform setup and cleanup tasks. See jestjs.io/docs/en/setup-teardown.

it (or test) block

Note

it is an alias for test. Use whichever word allows you to describe the expected behavior accurately.

An it block describes a single test. A test represents a single functional unit that you want to test. Write the it to describe the expected behavior of that function. For example, the hello component displays “Hello, World!”, so the it block tests that the hello component displays a greeting.

it('displays greeting', () => {
    ...
});

Create the component under test

The test uses the imported createElement method to create an instance of the component to test, in this case, example-hello.

const element = createElement('example-hello', {
    is: Hello
});

Add the component to the DOM

The test then calls appendChild to add the component to the test’s version of document.

The document.body.appendChild() call attaches the Lightning web component to the DOM and renders it. Which also means that renderedCallback() lifecycle method gets called.

document.body.appendChild(element);

The next step is to use a standard DOM query method to search the DOM for the element. Use element.shadowRoot as the parent for the query. It's a test-only API that lets you peek across the shadow boundary to inspect a component’s shadow tree. It’s the test equivalent of this.template.

const div = element.shadowRoot.querySelector('div');

Asserts

Finally, the expect statement is an assertion of the success condition: that the text of the element is “Hello, World!”

const div = element.shadowRoot.querySelector('div');
expect(div.textContent).toBe('Hello, World!');

Jest supports lots of matchers like toBe and toMatchObject that make it easy to check that a value meets a condition. See jestjs.io/docs/en/expect.

Test Asynchronous DOM Updates

When the state of a Lightning web component changes, the DOM updates asynchronously. To ensure that your test waits for updates to complete before evaluating the result, return a resolved Promise. Chain the rest of your test code to the resolved Promise. Jest waits for the Promise chain to complete before ending the test. If the Promise ends in the rejected state, Jest fails the test.

Example

This example is from the HelloExpressions recipe in the Lightning Web Components recipes app.

// helloExpressions.test.js
import { createElement } from 'lwc';
import HelloExpressions from 'recipe/helloExpressions';

const PREFIX = 'Uppercased Full Name:';

// Code removed for brevity

  it('displays first name as uppercase', () => {
    // Create initial element
    const element = createElement('example-hello-expressions', {
      is: HelloExpressions
    });
    document.body.appendChild(element);

    setInputElementValues(element, 'Peter', undefined);

    // Return a promise to wait for any asynchronous DOM updates. Jest
    // will automatically wait for the Promise chain to complete before
    // ending the test and fail the test if the promise rejects.
    return Promise.resolve().then(() => {
      // Verify displayed message
      const detailEl = element.shadowRoot.querySelector('p');
      expect(detailEl.textContent).toBe(`${PREFIX} PETER`);
    });
  });

// Code removed for brevity

}