Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation on "testing that a component dispatch an event" #179

Open
edmondop opened this issue Nov 7, 2020 · 19 comments
Open

Documentation on "testing that a component dispatch an event" #179

edmondop opened this issue Nov 7, 2020 · 19 comments

Comments

@edmondop
Copy link

edmondop commented Nov 7, 2020

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
@damianpoole
Copy link
Contributor

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

@edmondop
Copy link
Author

@damianpoole how am I even able to import it then?

import { ShowToastEventName } from "lightning/platformShowToastEvent";

@muenzpraeger
Copy link
Contributor

@edmondop
Copy link
Author

@muenzpraeger what needs to be done to stub specifically?

@muenzpraeger
Copy link
Contributor

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.

@muenzpraeger
Copy link
Contributor

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).

@edmondop
Copy link
Author

edmondop commented Nov 17, 2020

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 '/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

@damianpoole
Copy link
Contributor

Yes you would place your custom mock there or change the reference to a folder that better suits your project.

@edmondop
Copy link
Author

but the question is, why doesn't the import statement fail in npm if that constant is not exported?

@damianpoole
Copy link
Contributor

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.

@edmondop
Copy link
Author

edmondop commented Nov 17, 2020

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");

@damianpoole
Copy link
Contributor

damianpoole commented Nov 17, 2020

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

@edmondop
Copy link
Author

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

@muenzpraeger
Copy link
Contributor

@edmondo1984 Do you use the stub that I linked?

@edmondop
Copy link
Author

No @muenzpraeger I understood it was enough to perform this change in my unit test:

element.addEventListener("lightning__showtoast", handler);

@muenzpraeger
Copy link
Contributor

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.

@edmondop
Copy link
Author

Thanks, I didn't understood that. Can you please explain why?

@muenzpraeger
Copy link
Contributor

Because the detail property of the CustomEvent is not included in the default provided mock, that's the relevant modification in our custom version.

@edmondop
Copy link
Author

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants