With Lightning Web Components, we not only provide a technology based on modern web standards — we’re also providing the capability to unit test them locally or within your Continuous Integration flow with one of the most popular testing frameworks for modern JavaScript – Jest. In this blog post you’ll learn best practices for writing your own unit tests.
Why unit testing matters
In an ideal world, software wouldn’t have any bugs. Till we reach that point in time (likely when Skynet dominates the world), we as developers should all agree that we have to test our code as best as we can. When you look at the four quadrants of agile testing you’ll see that unit testing is part of quadrant 1 – the first line of defense for code quality. It’s also common knowledge that both unit testing and manual testing by the developer uncovers around 2/3 of errors before they hit the next testing quadrant. The earlier you find bugs, the “cheaper” they are, because once they hit the next stages of development (or in the worst case, production), more people and processes must get involved to get bugs identified and fixed. It can ultimately lead to the loss of trust in an application.
To showcase how to write your unit tests, over the last few weeks we added a lot of tests to our LWC Recipes sample application — and we made sure that they all pass. 😉
The basics
This blog post won’t explain the setup and how to run Jest tests. That is very well documented in the Lightning Web Components Developer Guide, which you should check out. You can also consult the official Jest website to learn more about Jest in general. What you will learn in this blog post is how a Jest test is defined, what the moving parts are, and how to handle them. For that let’s look at the test for the hello Lightning web component recipe.
Code explanation:
- Line 1: To create a new Lightning web component in a Jest test, we import the
createElement
function. This function is available only from tests. - Line 2: Import the Lightning web component that you want to test.
- Line 4: A
describe
defines a test suite. A test suite is a block that contains one or more tests. Use a test suite to cluster tests that belong together from a functional point of view. Usually you have only one, the outer test suite, in your test file. Based on your needs, you can create more test suites. - Line 5: Jest offers several convenience lifecycle methods like
afterEach()
, where you can run setup or cleanup tasks. - Line 12: An
it
describes a single test. It should read as the description of the expected behavior. A test represents a single functional unit that you want to test. That can be either that specific UI elements are present in your Lightning web component, that a specific function is called based on a DOM event, that an @wire adapter emits the correct data when called, and so forth. - Lines 14-17: These lines create the Lightning web component. The
document.body.appendChild()
call attaches the Lightning web component to the DOM and renders it, which also means thatrenderedCallback()
lifecycle method gets called. - Line 20: For querying any DOM element, you use
element.shadowRoot
as the parent for the query. It’s a test-only API that lets you peek through the Shadow DOM membrane to inspect a component’s internals. It’s equivalent tothis.template
but is accessible outside the component in tests. - Line 21: Jest offers a variety of matchers that let you check values and behaviors of various parts of your Lightning web component.
Now that you’ve seen an initial basic test, let’s talk about what you should focus on.
Only test the functionality of the component that you own
While this statement seems to explain the obvious, it’s something that I had to learn — sometimes the hard way. When you unit test your Lightning web component you have to focus on testing the functionality of your own component. You should not test any functionality of your child components. Whatever you expect your child components to do, that’s an implementation detail of each child component itself — and it should be covered within the tests of that child component.
Let’s look at two examples. The first HTML markup is from the recordFormDynamicContact recipe. It contains a single base Lightning component, lightning-record-form. When you look at the test for this recipe you will see that we validate if the properties (objectApiName
, recordId
, fields
) for the base component are correctly populated. We don’t test what the base component will actually render, because that’s an implementation detail of the base component.
The next markup is from the apiProperty recipe. This recipe sets the value of the percentage
property based on user input via the lightning-input base component. The test verifies that a change in the input field actually populates the chartBar
public property percentage
correctly. It doesn’t verify what will happen within the chartBar
component based on the property. This is covered in the test of the chartBar component itself.
To rinse and repeat: When you unit test your Lightning web component you have to focus on testing the functionality of the component being tested, not the functionality of its child components.
Code talks – time to explore the tests and their best practices
As mentioned before, we added a lot of tests to our LWC Recipes sample application. As every recipe focuses on one specific piece of functionality around Lightning Web Components, every test file showcases how to test it. We also added many comments to give you an easy head start to explore how they work.
There are many things to know and understand. We put together this list of best practices, recommendations, and rules that you should check out to make the best out of when crafting your own tests.
General
- Do not mix different test cases into one single test! Really focus on writing one test for one piece of functionality. It may look like writing a lot of boilerplate, but it also forces you to focus.
- Tests for a Lightning web component can be short or longer. Make sure you test what’s needed.
- Describe what your test should accomplish – you’ll have better focus when writing it and others will directly see what the test is about.
- Test that a component displays the expected outcome — be it displaying the expected UI elements, situations when no data is present, on errors, and so forth. Build a test for each of these scenarios. Bundle tests that focus on data and UI representation in test suites when it makes sense — that helps a lot with organization.
- You may not want to — or have to — test the order of UI elements. If you do, make sure that you use modern JavaScript to optimize the code when testing multiple elements at once.
Data (Apex and @wire)
- For testing imperative Apex calls, you have to mock the calls. Jest provides functionality to provision data and errors. As imperative Apex calls are, promises you have to flush them all before you can actually validate the UI.
- Put test data for your @wire tests relative to your test file. Also have mocks ready to simulate that no data is returned.
- For testing successful @wire calls you emit data, otherwise you error them.
- For testing, if your @wire adapter received the correct parameters, use
getLastConfig()
. - Don’t forget to clear your mocks. Otherwise you may find yourself in situations where testing them will fail.
- When you test if UI elements are filled based on mocked test data, re-use the mocked test data within the tests. That will improve consistency.
Salesforce specifics
- Every base Lightning component must be mocked and we provide a default mock automatically for you. That way you can test function execution and properties.
- When you want to test behavior that’s not covered by base Lightning components, you can create your own implementation. You make it available globally to your application using the jest.config.js file. This also applies to components which are not from your namespace, like from managed packages.
- If you execute code within lifecycle methods, you use
document.body.appendChild()
anddocument.body.removeChild()
to execute them. - When loading static resources you can either simply test how the UI behaves in case of a successful (or not successful) load without using the resource. Or you really load the static resource by overriding the default mock implementation.
- Test public properties on first render by setting them before the main element gets added to the document. That way they will be initialized correctly. You can always update them, but then you need to wait for the promise.
Testing techniques
- If you test JSON objects, use the
toEqual
matcher. For example, use it when you want to validate the detail parameter of a CustomEvent, and remember to mock the handler. - When you have to use timers in your Lightning web component, make sure to set your test up for that, and for running them before you validate the UI.
- You may have to add CSS classes or a
data-*
attribute to your Lightning web components markup to get the element that you want to test. This is because the parent component owns that, not the child component. The child also owns whether to reflect a property as an attribute in the DOM. - Depending on what you want to test you should remember that a promise can chain more than one
then()
. - You can override global APIs like
fetch
with your own mocks.
Check out the video walkthrough
You can find a walkthrough of creating your first Lightning web component test in our Lightning Web Components Video Gallery.
Summary
Having the capability to unit test Lightning Web Components with Jest offers developers a new level of quality assurance. As Jest is widely used, a lot of documentation and recommendations for testing with this framework is available. In an upcoming blog post we’re going to focus on more advanced tooling capabilities for your Lightning Web Components, like using Jest code coverage, or unit testing to your Continuous Integration flow.
Meanwhile, you should check out the Lightning Web Components documentation for testing, dig around what the lwc-jest tool does for you by providing mocks for all base Lightning components. And last but not least, explore the LWC Recipes tests to get started with your very first own set of tests. There is also this handy Jest cheatsheet which I highly recommend bookmarking.
About the author
René Winkelmeyer works as Principal Developer Evangelist at Salesforce. He focuses on enterprise integrations, Lightning, and all the other cool stuff that you can do with the Salesforce Platform. You can follow him on Twitter @muenzpraeger.