-
Notifications
You must be signed in to change notification settings - Fork 5
4) How to write a Jest test
Before we actually write our tests, we need to make a jest test file inside of our lightning web component folder! Let's go through the steps of creating a jest test for an LWC below:
-
If you haven't done so yet, make sure to create your LWC, we need to make it first so that we can put our jest test file in the same folder as our LWC!
-
Once your LWC is created, create a folder named "tests" inside your LWC folder.
-
After your folder "tests" is created inside your LWC's folder, create a new js file and name it "theNameOfYourLWC.test.js"
-
Batta Boom, Batta Bing, you ready to make a jest test now fam.
Note: In Illuminated Cloud 2 you can just right click on your LWC's folder, go to New -> "Lightning Web Component Javascript Test File" and it will do steps 2-3 for you automatically.
Depending upon the complexity of your Lightning Web Component, your jest test could be simple, or it could be complicated, but either way these basic concepts are important. Let's take a look at a very very veryyy basic jest test example and break it down piece by piece.
Simple jest test example:
describe('Addition function', ()=>{
test('2+3=5', ()=>{
const num = 2+3;
expect(num).toBe(5);
});
});
The above example is extremely simple, in fact, it's so simple it's not even testing an LWC! But you should see three key words in there. Those keywords are "describe", "test", and "expect". Let's go over each below:
describe - This keyword defines a test suite (or groups multiple tests together). Use this if you would like to group multiple similar types of tests together in a single block.
test - This keyword can also be replaced with the keyword "it", either one will work here, but I personally prefer the keyword "test" as I think it's more clear what you are writing, because what you are writing is indeed a test! The "test" keyword indicates the start of a single jest test method.
expect - This keyword is used when you want to test the value of something! For instance, we "expect" our variable named "num" "to be" the value 5 and what that expect keyword is doing above is checking to verify if that is true. It's very similar to the Assert class that you use when writing tests in Apex! If you would like to see a list of all of the "matchers" that can be appended to the end of the "expect" keyword (like "toBe" in the example above), you can check out a list of all of them here.
In Apex when you want to have something setup for you prior to a test method running you would use a method annotated with @testSetup. The beforeEach and the afterEach functions in jest work in the same way. beforeEach runs code before each of your jest tests run and the afterEach funtion runs after each jest test runs. Pretty niftyyyyy. Let's check that magic out in action.
beforeEach Example:
beforeEach(()=>{
console.log('I run before your test runs!');
});
afterEach Example:
afterEach(()=>{
console.log('I run after your test runs!');
});
The above examples are obviously very simple examples just to give you an idea of how these methods are setup and how they work, in reality you will likely be setting up or tearing down html elements that you need for testing in your jest tests in these methods, should you decide to use them. Just as a reminder though, these methods run before or after EVERY SINGLE JEST TEST. So make sure you need the code in them running before each test and that the code won't be detrimental to any of the individual jest tests in your test js file.
More than likely the vast majority of your LWC's have a UI component to them and the only way to properly test them would be to test that values actually were populated and displayed in the html file of your LWC. You might be thinking, "Yea, you right, but how the heck can I test that? No one is viewing the lwc during the jest test...". No worries fam, there's an answer to that in the form of "import {createElement} from 'lwc'". We're gonna use that import to build our LWC's html so we can test it properly! Yay!!
Let's take a look at a simple example of this in action:
import {createElement} from 'lwc';
import componentYouAreTesting from 'c/componentYouAreTesting';
describe('componentYouAreTesting Test Suite", ()=>{
test('example creation of dom element', ()=>{
const testingComponent = createElement('c-component-you-are-testing', {
is:componentYouAreTesting
});
document.body.appendChild(testingComponent);
});
});
The above code has successfully appended the lightning web component named "componentYouAreTesting" to the DOM and now you will be able to test whether or not data gets appended to that element successfully! Let's figure out how to setup a bit more complicated scenarios below, and how we can actually verify DOM elements get filled out as expected.
Data binding in your html file of your LWC is likely one of the most common things you do in all of your LWC's. In this section we're gonna figure out exactly how to test that those data binds are displaying exactly what you intended for them to test. So let's get to it!
Ok, so first things first, we need an actual LWC this time with both an html and a js file. I have a prebuilt example below. The name of the prebuilt LWC is "jest_data_bind_example" (this is important when we get to the actual jest test further down):
jest_data_bind_example JS File:
import {LightningElement} from 'lwc';
export default class JestDataBindExample extends LightningElement {
paragraphText = "Hi I'm a Talapia";
}
jest_data_bind_example HTML File:
<template>
<p>{paragraphText}</p>
</template>
What we're gonna do next is build a jest test that will allow us to check that the value of that bind variable is indeed "Hi I'm a Talapia". Let's check out how to do that below with a jest example:
Jest File:
import {createElement} from 'lwc';
import jestDataBindExample from 'c/jest_data_bind_example';
describe('dataBindTests', ()=>{
test('checkingParagraphText', ()=>{
//Creating the jest_data_bind_example using the createElement command, so that we can test it
const paragraphComponent = createElement('c-jest_data_bind_example', {
//verifying we made the correct element
is:jestDataBindExample
});
//Appending the jest_data_bind_example component to the document/DOM
document.body.appendChild(paragraphComponent);
//Finding our paragraph tag we've placed our variable in
const paragraphText = paragraphComponent.shadowRoot.querySelector('p');
//Verifying that we indeed have the text Hi I'm a Talapia in the paragraph tag
expect(paragraphText.textContent).toBe("Hi I'm a Talapia");
});
});
As you can see, this is thankfully not particularly hard to do at all! In the jest test we just create our component, place the component into the DOM (Document Object Model), find our paragraph element, and then verify that the textContent of that paragraph element is indeed what we believe it to be. Pretty nice and easy lemon squeezy, but then again, this is a very simple example, as we go on you will see things get slightly more complicated, but if you read each of these sections in order, by the time you get to the complicated stuff it'll be nbd.
Chances are in the vast majority of your LWC's you're going to have js events incorporated somewhere, maybe a simple onchange event, or maybe a CustomEvent you made for your particular needs! In any event, we need to learn how to test those bad boiz, so let's get to it!
First things first, before we get to the jest test we need to ensure that we have a component with an actual event in it. So, let's just continue to add on to our existing component that we started building in the data binding section and give it a button that will change the value of the bind variables text on the click of the button.
LWC HTML File:
<template>
<p>{paragraphText}</p>
<lightning-button onclick={changePText}>Get New Paragraph Text</lightning-button>
</template>
LWC JS File:
import {LightningElement} from 'lwc';
export default class JestDataBindExample extends LightningElement {
paragraphText = "Hi I'm a Talapia";
changePText(){
this.paragraphText = "Hi I'm a taco";
}
}
LWC Jest Test File:
import {createElement} from 'lwc';
import jestDataBindExample from 'c/jest_data_bind_example';
describe('dataBindTests', ()=>{
beforeEach(()=>{
//Creating the jest_data_bind_example using the createElement command, so that we can test it
const lwc_jest_example_component = createElement('c-jest_data_bind_example', {
//verifying we made the correct element
is:jestDataBindExample
});
//Appending the jest_data_bind_example component to the document/DOM
document.body.appendChild(lwc_jest_example_component);
});
test('testingOnClickEvent', ()=>{
const lwc_jest_example_component = document.querySelector('c-jest_data_bind_example');
const button = lwc_jest_example_component.shadowRoot.querySelector('lightning-button');
//triggering the onclick event on the button
button.dispatchEvent(new CustomEvent('click'));
//Waiting for our onclick event to finish
return Promise.resolve().then(()=>{
//Finding our paragraph tag we've placed our variable in
const paragraphText = lwc_jest_example_component.shadowRoot.querySelector('p');
//Verifying that we indeed have the text Hi I'm a Taco in the paragraph tag after the event is run
expect(paragraphText.textContent).toBe("Hi I'm a taco");
});
});
});
As you can see from the above files we've now added a button with the onclick event to the LWC. The onclick will alter the text in our bind variable to the value "Hi I'm a taco". Now we just need to test it!
To do so we are finding the button in the DOM here const button = lwc_jest_example_component.shadowRoot.querySelector('lightning-button');, once we find the button we need to click it somehow to actually force the event to take place, and we do so by dispatching a custom event from it as show here button.dispatchEvent(new CustomEvent('click'));.
We have now successfully dispatched our event in our jest test! However we need to wait for our event to finish (since events occur asynchronously in JavaScript), we can do so using Promise.resolve(). After waiting for our event to finish we can now check the content of the paragraph texts bind variable to see if our event updated it successfully.
Conditional rendering is pretty common in most LWC's, and making sure that an element is only rendered in the right scenario is crucial to ensuring your component works as intended (pending that's a feature of your LWC of course lol). In this section we're gonna find out how to test those scenarios, so let's check, check, check it outttt.
Just like in the previous two examples, first we need a component that actually has conditional rendering, so let's add that to the component we've been testing throughout this guide.
LWC HTML File:
<template>
<p>{paragraphText}</p>
<lightning-button onclick={changePText}>Get New Paragraph Text</lightning-button>
<template lwc:if={renderImage}>
<img src="https://coolimage.png"/>
</template>
</template>
LWC JS File:
import {LightningElement} from 'lwc';
export default class JestDataBindExample extends LightningElement {
paragraphText = "Hi I'm a Talapia";
renderImage = false;
changePText(){
this.paragraphText = "Hi I'm a taco";
}
}
As you can see in the files above, we've added some a conditionally rendered image to our html
<template lwc:if={renderImage}>
<img src="https://coolimage.png"/>
</template>
And in our JS file we have declared the renderImage variable to be false renderImage = false;. Now we've just gotta test to make sure that the element does not render when the component is in the page! Let's check out the jest test to do this now!
LWC Jest Test:
import {createElement} from 'lwc';
import jestDataBindExample from 'c/jest_data_bind_example';
describe('dataBindTests', ()=>{
beforeEach(()=>{
//Creating the jest_data_bind_example using the createElement command, so that we can test it
const lwc_jest_example_component = createElement('c-jest_data_bind_example', {
//verifying we made the correct element
is:jestDataBindExample
});
//Appending the jest_data_bind_example component to the document/DOM
document.body.appendChild(lwc_jest_example_component);
});
test('testRenderOfImage', ()=>{
const lwc_jest_example_component = document.querySelector('c-jest_data_bind_example');
const image = lwc_jest_example_component.shadowRoot.querySelector('img');
expect(image).toBeNull();
});
});
The name of our new test is the "testRenderOfImage" and it's a pretty simple test huh? Basically all we're doing is loading our LWC into the page and then looking for the img element we added and expecting it toBeNull since our boolean variable "renderImage" in the JS was set to false. Of course if you were testing the reverse (boolean variable set to true) you'd want that statement to be expect(image).not.toBeNull.
Quick, simple and painless right? I told you they'd get easier the further along we got in this guide!
Iterators, for:each and everything inbetween are gonna show up in tons of LWC's you make, and figuring out how to test them isn't the most obvious thing in the world. Let's go through a couple examples together on how to test iterators of every kind in your LWC's.
Let's get right to it and update our component so that it has an iterator in the html that we can actually test!
LWC HTML:
<template>
<p>{paragraphText}</p>
<lightning-button onclick={changePText}>Get New Paragraph Text</lightning-button>
<template lwc:if={renderImage}>
<img src="https://coolimage.png"/>
</template>
<template for:each={tacoList} for:item="taco">
<p class="tacoInfo" key={taco.Id}>
{taco.TacoType}
</p>
</template>
</template>
LWC JS
import {LightningElement} from 'lwc';
export default class JestDataBindExample extends LightningElement {
paragraphText = "Hi I'm a Talapia";
renderImage = false;
tacoList = [{Id:'1', TacoType:'Chalupa'}, {Id:'2', TacoType: 'Crunchy'}];
changePText(){
this.paragraphText = "Hi I'm a taco";
As you can see we have added the following code to our JS file tacoList = [{Id:'1', TacoType:'Chalupa'}, {Id:'2', TacoType: 'Crunchy'}];. This is the array of elements that we will be iterating over to display in our HTML file.
In our HTML file we have added the following
<template for:each={tacoList} for:item="taco">
<p class="tacoInfo" key={taco.Id}>
{taco.TacoType}
</p>
</template>
What this does is take our "tacoList" JS variable and iterate over it to produce paragraph elements for each item in our "tacoList" array. We are going to test in our jest test that this does indeed create two paragraph elements in our component and we are going to test that the values of the paragraph elements are correct. Let's take a look at the jest test now.
LWC Jest Test:
import {createElement} from 'lwc';
import jestDataBindExample from 'c/jest_data_bind_example';
const TACO_VALUES = ['Chalupa', 'Crunchy'];
describe('dataBindTests', ()=>{
beforeEach(()=>{
//Creating the jest_data_bind_example using the createElement command, so that we can test it
const lwc_jest_example_component = createElement('c-jest_data_bind_example', {
//verifying we made the correct element
is:jestDataBindExample
});
//Appending the jest_data_bind_example component to the document/DOM
document.body.appendChild(lwc_jest_example_component);
});
test('listIterationDisplay', ()=>{
const lwc_jest_example_component = document.querySelector('c-jest_data_bind_example');
//Creating an array/list from our content created from our iterator
const tacoList = Array.from(lwc_jest_example_component.shadowRoot.querySelectorAll('.tacoInfo'));
//Cleaning up our array so that it's only a list of the text content in our iterated elements
const tacoMap = tacoList.map(p => p.textContent);
expect(tacoMap.length).toBe(2);
//Use toEqual to compare objects, maps or lists
expect(tacoMap).toEqual(TACO_VALUES);
});
});
As you can see this test is not incredibly complicated but it may be confusing if you aren't comfortable with some JS methods used within it. We are basically just grabbing all of our paragraph elements that we have created and placing them into an array in our lwc with this line const tacoList = Array.from(lwc_jest_example_component.shadowRoot.querySelectorAll('.tacoInfo'));, then we are cleaning up that array to make it less ugly and only contain the textContent of our paragraph elements here const tacoMap = tacoList.map(p => p.textContent);. After we make our clean array of our paragraph element values, we then check to ensure we only have two paragraph elements (since we only have two items in our array) and we check to ensure we have generated the right values in that array.
Pretty simple, but it does require some comfortability with JS Array methods so definitely check those out if the above code makes you a little uncomfortable.
Got a component inside your component? Child components are likely a part of any quality made LWC and we absolutely need to know how to test that they are reacting right as well, so let's run through an example together of how to figure out whether your parent LWC's sweet cute little child LWC is working as expected below.
You, me, and your best friend Jean Ralphio have all got @wire methods in our LWC's whether we like it or not, let's figure out how to test those cutie pies below.
If you aren't callin Apex somewhere in your LWC, was it even worth writing? Probably not. Lol, ok, that's definitely not true, but we still need to know how to test those Apex calls within our LWC's when we make them, so let's figure out how, ok pal??
If you haven't used LMS yet, honestly you are missin out. It's one of the more complicated things to test, but it's not crazy hard, we'll just have to use more of that stubbing we found out about when testing apex class calls! Let's check check check it outttttt.