How to mock a child LWC in a parent’s Jest Unit Test

lightning-web-componentslwc-jestmock

I have two components that are in my app:

  • parentComponent
  • childComponent.

'parentComponent' includes 'childComponent' as a component.

E.g: parentComponent.html

<template>
    <c-child-component
        child-property={aValue}
        other-child-property={anotherValue}
    ></c-child-component>
</template>

Child component is non-trivial, and maybe includes some @wire methods or imperative calls to Apex.

Therefore, when writing the Jest unit test for parentComponent we want to mock childComponent so we don't have to worry about its complex behaviour – particularly when it references the database.

The Salesforce documentation (at time of writing) describes mocking LWCs in both the following links:

This involves creating a jest.config.js file and defining moduleNameMapper references.

Unfortuantely, this will change the behaviour of the import so that it includes the mock version in all tests.

In the described situation, we have build childComponent and therefore need to also test this component – and so we can't use that technique.

So – How can I mock childComponent only in the unit test for parentComponent?

Best Answer

In order to mock childComponent only in the test for parentComponent we need to:

  • Build a skeleton stub for childComponent as a JS file.
  • Instruct the parentComponent test to use this file instead of the real childComponent.

Build a skeleton stub for childComponent as a JS file.

  • Create a folder inside the childComponent's folder named __mocks__.
  • Inside that folder, create a file childComponent.js, copying the contents of the real component into it. You should end up with a folder structure like:
    lwc
        childComponent
            __mocks__
                childComponent.js
            __tests__
                childComponent.test.js
            childComponent.html
            childComponent.js
            childComponent.js-meta.xml
  • Strip out everything from the new file other than the class definition and @api defined properties. E.g:
import { LightningElement, api } from 'lwc';

export default class ChildComponent extends LightningElement
{
    @api childProperty;
    @api otherChildProperty;

    @api doThing() {}  // include any @api annotated methods
}
  • Ensure that the mocks folders are ignored when you perform a push by adding **/__mocks__/** into your .forceignore. E.g. it may look like:
package.xml

# LWC configuration files
**/jsconfig.json
**/.eslintrc.json

# LWC Jest
**/__tests__/**
**/__mocks__/**

Instruct the parentComponent test to use this file instead of the real childComponent.

  • At the top of the unit test for parentComponent, tell Jest to use the mock version of childComponent by calling jest.mock passing the Salesforce style import path of the component. E.g. the top of your test file may look like:
import { createElement } from 'lwc';
import ParentComponent from 'c/parentComponent';

jest.mock( 'c/childComponent' ); // Uses the mock version of childComponent defined in the __mocks__ directory of that LWC

describe( 'c-parent-component', () => {
    // your tests...
}

Stretch goal: Mocking methods that exist on the child component.

  • If you need to then check that methods are called on your child component, then you can grab the child from the parent component and configure a mock function for any that you are interested in.

For example:

describe('c-parent-component', () => {

    beforeEach( () => {
        const element = createElement('c-parent-component', {
            is: ParentComponent
        });

        document.body.appendChild( element );
    })

    it( 'Child component is called', () => {

        const element = document.body.querySelector( 'c-parent-component' );
        const child = element.shadowRoot?.querySelector( 'c-child-component' ); // Get the child component from the parent's DOM
        child.doThing = jest.fn();  // override the method on child to be a mock.  You can define its behaviour at this point too.

        return Promise.resolve()
            .then( () => element.shadowRoot?.querySelector( 'lightning-button' ).click() )
            .then( () => {
                expect( child.doThing ).toHaveBeenCalledTimes( 1 );  // check that the child method was called.  You can check parameters were passed, etc.
            })
    });

}

Note: in general, I would use more identifying characteristics on the component other than simply the type of HTML element it is. E.g. I would add a data-id or data-name and use that instead.

(thanks to https://tigerfacesystems.com/blog/lwc-nested-component-testing for the basic solution to this problem)