# 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
-
To install Jest, run this command inside your package:
npm install jest --save-dev
-
To install
@lwc/jest-preset
, run this command inside your package:npm install @lwc/jest-preset --save-dev
-
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
}