Foglight Solutions - Jest Unit Testing with LWC

Jest Unit Testing with LWC

March 25, 2020 by Reis Warman

LWC Intro

Lightning Web Components is the newest programming model built to aid in developing a new breed of Lightning Components. Unlike Aura, LWC sets out to leverage the feature rich browsers we have come to know within the last few years, all while maintaining the ability to create lightweight, feature packed components and applications. LWC components can live right along-side Aura components and utilizes the same underlying services, such as Lightning Data Service, providing seamless integration. Part of the responsibility placed on developers creating custom Lightning Components is thorough regression, unit and functional testing. Before LWC there was not a clear path for front-end unit testing Lightning Components… enter Jest.

Jest Intro

When approaching the testing of your components, it’s important to remember the two types of testing, functional and unit. Salesforce.com briefly mentions in their documentation here that functional (end-to-end) testing is not recommended for Lightning Web Components; however, Jest unit testing is strongly encouraged. Jest is a Javascript testing framework developed with an emphasis on simplicity. These tests run locally and will not be pushed to Salesforce.com as part of the component bundle. Each test lives in a separate folder inside of the component bundle and contain any number of tests inside test suites.

Lightning Web Components provide a tremendous amount of pre-built functionality that can be used to speed up development and prevent rework. One of the best examples of this is the platformShowToastEvent module you can import into any of your components. This provides a JS class ShowToastEvent that can be used to dispatch configurable toast messaging on your component. After implementing this module you will inevitably need to write a front-end Jest unit test that will validate your toasts are being fired properly and in the correct scenarios. Let’s look at the best pattern for validating the firing of events when writing jest unit tests.

Finally Some Code…

Below is a simple component named toastExample that we will use to demonstrate some functionality and write a unit test which validates when toasts are fired. toastExample.html contains a single <lightning-button> component with a onclick handler attribute which calls our function showToast.

1
2
3
4
5
6
<template>
    <lightning-button
        label="Show Toast"
        onclick={showToast}>
    </lightning-button>
</template>

Our JavaScript controller toastExample.js contains two import statements, one that imports the base LightningElement from the lwc module and ShowToastEvent from lightning/platformShowToastEvent which gives us the ability to fire toasts. Inside showToast() we instantiate a new ShowToastEvent and pass in an object with a title and message attribute. Next, we call dispatchEvent with our event as a parameter and our toast should be displayed. So now let’s take a look at the Jest unit test that we would need to write in order to test the toastExample component.

1
2
3
4
5
6
7
8
9
10
11
12
import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent'

export default class MyComponent extends LightningElement {
    showToast() {
        const event = new ShowToastEvent({
            title: 'Toast Title',
            message: 'Here is our toast message',
        });
        this.dispatchEvent(event);
    }
}

Below is our Jest unit test named toastExample.test.js. First thing we do is import createElement from the lwc module and the component we will be testing, in our case ToastExample from c/toastExample. According to the salesforce documentation we should then import ShowToastEventName from lightning/platformShowToastEvent which would aid us in testing ShowToastEvent. We will discuss what that is for in a minute but for now ignore it and the commented constant below that.

We have now arrived at the beginning of our test suite definition : describe('c-toast-example', () => {});. All of our tests will be contained in the anonymous function being passed into the second parameter and can be contained in their own describe functions or live directly under the main describe function. For our simple scenario we will leave our single test directly under the main describe function.

Continuing on, we define our test using the test function ( is() may be used in the same way) which creates an element for the component we will be testing and append it into the document body. At this point we have laid the groundwork to build our testing scenario.

Let’s review what we are trying to accomplish here. We want to test that the toast is being fired when we click our button so we need to listen to whatever event is being fired in order to pop the toast. You may be wondering, “Well, what is that event called?”. According to the documentation this event can be imported as ShowToastEventName ( which we did earlier ).

We need to define a mock function and assign it to our constant:

1
const showToastHandler = jest.fn();

Our mock function showToastHandler will be used as a callback function to an event listener on ToastExampleElement. It will be listening for the ShowToastEventName so this is how we do that:

1
2
3
4
ToastExampleElement.addEventListener(
    ShowToastEventName,
    showToastHandler
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { createElement } from 'lwc';
import ToastExample from 'c/toastExample';

import { ShowToastEventName } from 'lightning/platformShowToastEvent';
//const SHOW_TOAST_EVT = 'lightning__showtoast';

describe('c-toast-example', () => {

    afterEach(() => {
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
        jest.clearAllMocks();
    });

    test('the ShowToastEvent is fired when the user clicks the button', () => {
        const ToastExampleElement = createElement('c-toast-example', {
            is: ToastExample
        });
        document.body.appendChild(ToastExampleElement);

        const showToastHandler = jest.fn();
        ToastExampleElement.addEventListener(ShowToastEventName, showToastHandler);
        //ToastExampleElement.addEventListener(SHOW_TOAST_EVT, showToastHandler);

        return Promise.resolve().then(() => {
            const showToastBtn = ToastExampleElement.shadowRoot.querySelector('lightning-button');
            showToastBtn.click();

        }).then(()=> {
            expect(showToastHandler).toBeCalledTimes(1);
        });
    });
});

Now we expect that as soon as the button is clicked the ShowToastEventName event will be fired and since our component ToastExampleElement is listening for that event it would call its jest mock callback function showToastHandler.

Once our promise resolves, we query for our button and call the click function. We need to wait for our promise to resolve so after it does we write our assertion inside of the chained then statement.

1
expect(showToastHandler).toBeCalledTimes(1);

We can run our test suite now and see if we pass.

LWC Test Result 1

It looks like our expect function failed and the mock function was never called meaning that the event we were listening to was never fired. Let’s instead manually define the event name and reference it as we do below:

1
const SHOW_TOAST_EVT = lightning__showtoast;

Also, we need to update or event listener from ShowToastEventName to our manually defined constant SHOW_TOAST_EVT by uncommenting the line below.

1
ToastExampleElement.addEventListener(SHOW_TOAST_EVT, showToastHandler);

After our second run this is our result:

LWC Test Result 2

It passed! So we found that when a toast is fired it’s event name is lightning__showtoast but exporting ShowToastEventName from platformShowToastEvent.js will return undefined. If we listen to this event then we should be able to keep track of when toasts are being fired.

After getting to use Jest for a few months I have found it to be a tremendous pleasure to work with and a welcomed companion to LWC. There is always a learning curve when starting with a new framework but I hope my thoughts above will help simplify someone’s journey on their path to learning LWC and Jest!

ABOUT THE AUTHOR

Reis Warman | Consultant

Reis is a Consultant at Foglight and is expert at all things Apex, LWC, and Visualforce. He is passionate about improving business operations and creating happy customers through the end to end consulting process.