LWC Jest – How to Track Calls to Child Component’s @api-decorated Functions

I struggled with this and couldn't find anything on Salesforce StackExchange or Google covering this kind of scenario in an LWC-specific context. So I'm going to post something Q&A-style.

I have a parent LWC with a child component and button like this:

<c-child></c-child>
<lightning-button label="Click me" onclick={handleClick}>
</lightning-button>

On button click, the parent component calls an @api-decorated function on the child, like this:

handleClick(event) {
    this.template.querySelector("c-child").sayHello(event.target.label);
}

For this example, I want to write a Jest test that asserts that the child component's @api-decorated sayHello function was called with a single argument: "Click me".

I initially expected the following to work, but it failed:

it("sends stuff to the child component", () => {
    const element = createElement("c-parent", {
        is: parent
    });
    document.body.appendChild(element);

    // Set up an event listener on the child
    const child = element.shadowRoot.querySelector("c-child");
    const sayHelloHandler = jest.fn();
    child.addEventListener("sayHello", sayHelloHandler);
    
    // Simulate the user clicking the lightning-button
    const button = element.shadowRoot.querySelector("lightning-button");
    button.dispatchEvent(new Event("click"));

    // THIS FAILS -- sayHelloHandler shows 0 calls
    expect(sayHelloHandler).toHaveBeenCalledWith("Click me");
});

Best Answer

I was able to introspect calls to the child component's function using jest.spyOn(), like this:

it("sends stuff to the child component", () => {
    const element = createElement("c-parent", {
        is: parent
    });
    document.body.appendChild(element);

    // Use jest.spyOn() to track calls to the child component's function
    const child = element.shadowRoot.querySelector("c-child");
    const spy = jest.spyOn(child, "sayHello");

    // Simulate the user clicking the lightning-button
    const button = element.shadowRoot.querySelector("lightning-button");
    button.dispatchEvent(new Event("click"));

    // No longer failing
    expect(spy).toHaveBeenCalledWith("Click me");
});

See Jest docs