sfdx-lwc-jest
sfdx-lwc-jest copied to clipboard
Documentation on "testing that a component dispatch an event"
Description
The example shows how to test events dispatching, but events are dispatched explicitly. https://github.com/trailheadapps/lwc-recipes/blob/master/force-app/main/default/lwc/miscNotification/tests/miscNotification.test.js
One additional pattern is instead verifying that for example, upon invoking a wire service that returns an error, the component dispatch the event, i.e. we do not want to dispatch the event explicitly.
Steps to Reproduce
import MyComponent from "c/myComponent";
import { registerLdsTestWireAdapter } from "@salesforce/sfdx-lwc-jest";
import myMethod from "@salesforce/apex/MyController.MyMethod";
import { ShowToastEventName } from "lightning/platformShowToastEvent";
import { createElement } from "lwc";
const mockMyMethod = registerLdsTestWireAdapter(
myMethod
);
const mockErrorResponse = require("./data/errorResponse.json");
describe("My Component", () => {
afterEach(() => {
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it("should correctly show a toast when the API returns an error", () => {
const element = createElement("c-my-component", {
is: MyComponent,
});
const handler = jest.fn();
document.body.appendChild(element);
element.addEventListener(ShowToastEventName, handler);
myMethod.error(
mockErrorResponse.body,
mockErrorResponse.status
);
return Promise.resolve().then(() => {
expect(handler).toHaveBeenCalled();
});
});
});
<!-- HTML for component under test -->
<template>
...
</template>
import { api, LightningElement, wire, track } from "lwc";
import myMethod from "@salesforce/apex/MyController.MyMethod";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
export default class MyComponent extends LightningElement {
@api recordId = "who-cares";
@track
data;
@wire(myMethod, { myObjId: "$recordId" })
wiredRecord({ data, error }) {
if (data) {
this.data = data;
} else {
if (error) {
let errorMessage = "Unknown error";
if (Array.isArray(error.body.message)) {
errorMessage = error.body.message.map((e) => e.message).join(", ");
} else if (typeof error.body.message === "string") {
errorMessage = error.body.message;
}
this.dispatchEvent(
new ShowToastEvent({
title: "Error when invoking the Controller API",
message: errorMessage,
variant: "error",
})
);
}
}
}
}
# Command to repro
sfdx-lwc-jest -- --no-cache
Expected Results
the Jest handler is invoked once
Actual Results
Expected number of calls: >= 1 Received number of calls: 0
Version
- @salesforce/sfdx-lwc-jest: 0.10.2
- Node: 12.12.0
This issue here is that the platformShowToast stub does not export the event name so your handler in your test is never configured correctly.
https://github.com/salesforce/sfdx-lwc-jest/blob/master/src/lightning-stubs/platformShowToastEvent/platformShowToastEvent.js
@damianpoole how am I even able to import it then?
import { ShowToastEventName } from "lightning/platformShowToastEvent";
Did you stub it? https://github.com/trailheadapps/lwc-recipes/blob/master/force-app/test/jest-mocks/lightning/platformShowToastEvent.js
@muenzpraeger what needs to be done to stub specifically?
ShowToastEventName
is not exported, as @damianpoole already pointed out. That's one of the changes in the custom mock that I shared.
You would have to hardcode lightning__showtoast
in your test, and not import, if you want to use the standard mock. Also note that the standard mock does not include the event details. So if you want to test any of the details, like variant, message etc, you'd have also to use the custom mock.
Also see here the Jest config to us the mock.
Using this custom mock for platformShowToastEvent
is also described in the official LWC Jest documentation here (search for Module Imports
).
Just to be sure, there are two ways I can act:
- create a custom mock and change my jest configuration
- hardcode the name
When the jest config mention these folders, should I place my custom mocks there? or are these available as a consequence of importing some additional npm modules? i.e. who places something in '<rootDir>/force-app/test/jest-mocks/lightning/navigation' ?
const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config');
module.exports = {
...jestConfig,
moduleNameMapper: {
'^@salesforce/apex$': '<rootDir>/force-app/test/jest-mocks/apex',
'^lightning/navigation$':
'<rootDir>/force-app/test/jest-mocks/lightning/navigation',
'^lightning/platformShowToastEvent$':
'<rootDir>/force-app/test/jest-mocks/lightning/platformShowToastEvent',
'^lightning/uiRecordApi$':
'<rootDir>/force-app/test/jest-mocks/lightning/uiRecordApi'
}
};
Thanks a lot for your help
Yes you would place your custom mock there or change the reference to a folder that better suits your project.
but the question is, why doesn't the import statement fail in npm if that constant is not exported?
AFAIK because you are creating a variable called ShowToastEventName
, it is not being set to anything that is exported therefore the variable exists it is just undefined.
Now the handler is getting triggered, except that the event.detail is null in the handler so if I had the following assertion, the test fails
expect(handler.mock.calls[0][0].detail.title).toBe(
"Error when invoking the Controller API"
);
expect(handler.mock.calls[0][0].detail.variant).toBe("error");
Does your mock constructor pass the detail along like the example @muenzpraeger linked?
https://github.com/trailheadapps/lwc-recipes/blob/master/force-app/test/jest-mocks/lightning/platformShowToastEvent.js#L10
I am not mocking the event, it is dispatched from within the component. I hardcoded the event name and subscribed to it in my test
@edmondo1984 Do you use the stub that I linked?
No @muenzpraeger I understood it was enough to perform this change in my unit test:
element.addEventListener("lightning__showtoast", handler);
As mentioned earlier:
If you want to test any of the details, like variant, message etc, you'd have also to use the custom mock.
Thanks, I didn't understood that. Can you please explain why?
Because the detail
property of the CustomEvent is not included in the default provided mock, that's the relevant modification in our custom version.
Excellent, thank you. It was basically me getting confused, maybe I can submit a PR to the documentation saying that you need to be careful about default stubs not stubbing all the properties?