salesforce / sfdx-lwc-jest

Run Jest against LWC components in SFDX workspace environment
MIT License
164 stars 81 forks source link

Issue when test connectedCallback method on LWC component that use more that one module mock #304

Open vdyn opened 1 year ago

vdyn commented 1 year ago

Description

connectedCallback method does not complete calls to mock modules before tests start.

Steps to Reproduce

customComponent.js

import {api, LightningElement} from "lwc";
import getResult01 from "@salesforce/apex/Controller01.getResult01";
import getResult02 from "@salesforce/apex/Controller02.getResult02";

export default class CustomComponent extends LightningElement {

    value01;
    value02;

    @api
    getValue01() {
        return this.value01;
    }

    @api
    getValue02() {
        return this.value02;
    }

    async connectedCallback() {
        await this.initialization();
    }

    @api
    async customMethod() {
        await this.initialization();
    }

    async initialization() {
        console.log("starting callback");

        const result01 = await getResult01();
        console.log("result 01 is", result01);
        this.value01 = result01.field0101;

        const result02 = await getResult02();
        console.log("result 02 is", result02);
        this.value02 = result02.field0201;

        console.log("ending callback");
    }
}

customComponent.test.js

import {createElement} from 'lwc';
import getResult01 from "@salesforce/apex/Controller01.getResult01";
import getResult02 from "@salesforce/apex/Controller02.getResult02";
import CustomComponent from "../customComponent";

jest.mock(
    "@salesforce/apex/Controller01.getResult01",
    () => {
        return {default: jest.fn()};
    },
    {virtual: true}
);

jest.mock(
    "@salesforce/apex/Controller02.getResult02",
    () => {
        return {default: jest.fn()};
    },
    {virtual: true}
);

async function flushPromises() {
    return Promise.resolve();
}

const createComponent = async (attributes) => {
    const element = createElement("custom-component", {
        is: CustomComponent
    });
    Object.assign(element, attributes);
    document.body.appendChild(element);

    await flushPromises();
    return element;
};

describe("custom component test", () => {

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

    it("connectedCallback", async () => {
        getResult01.mockResolvedValue({
            field0101: "a",
            field0102: "b"
        });
        getResult02.mockResolvedValue({
            field0201: "c",
            field0202: "d"
        });
        const element = await createComponent();
        window.console.log("value 01", element.getValue01());
        window.console.log("value 02", element.getValue02());
    });

    it("customMethod", async () => {
        getResult01.mockResolvedValue({
            field0101: "a",
            field0102: "b"
        });
        getResult02.mockResolvedValue({
            field0201: "c",
            field0202: "d"
        });
        const element = await createComponent();
        await element.customMethod();
        window.console.log("value 01", element.getValue01());
        window.console.log("value 02", element.getValue02());
    });
});

Expected Results

First tests work as expected. Please note that the second test is for check the correct code flow.

Actual Results

First test fails. The first value (the result to call getResult01) is correct, but the second value (the result to call getResult02) is undefined.

Version

Possible Solution

No solution found.

Additional context/Screenshots

BatemanVO commented 1 year ago

I copy/pasted the code and ran the test and both pass for me:

PASS force-app/main/default/lwc/customComponent/__tests__/customComponent.test.js
  custom component test
    √ connectedCallback (143 ms)
    √ customMethod (15 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.918 s
Ran all test suites matching /force-app\\main\\default\\lwc\\customComponent\\/i.

Using:

Can you consolidate the Apex into a single call? It seems strange to make two calls to Apex in a connectedCallback instead of a single call that would return the data both calls would make, since each call is a separate trip to the server and it would be more efficient to bundle the needed data in a single call.

For example, if you have:

@AuraEnabled
public static Map<String, String> getResult01() {
    return new Map<String, String>{
        'field0101' => 'a',
        'field0102' => 'b'
    };
}

@AuraEnabled
public static Map<String, String> getResult02() {
    return new Map<String, String>{
        'field0201' => 'c',
        'field0202' => 'd'
    };
}

Then you could do something like:

public static Map<String, String> getResult01() {
    return new Map<String, String>{
        'field0101' => 'a',
        'field0102' => 'b'
    };
}

public static Map<String, String> getResult02() {
    return new Map<String, String>{
        'field0201' => 'c',
        'field0202' => 'd'
    };
}

@AuraEnabled
public static Map<String, Map<String, String>> getResults() {
    return new Map<String, Map<String, String>>{
        'result01' => getResult01(),
        'result02' => getResult02()
    };
}

And in the initialization:

async initialization() {
    const results = await getResults();
    this.value01 = results.result01.field0101;
    this.value02 = results.result02.field0201;
}
nolanlawson commented 1 year ago

@vdyn Could you provide a GitHub repo with the necessary code and steps to reproduce the issue?