3

Can we simulate an event subscription in LWC unit test? I have a lightning web component using lighting/empApi like,

export default class ExampleComponent extends LightningElement {
    connectedCallback() {
        const onMessageCallback = (response) => {
            if (this.isSomething) { //Not a public (@api) parameter
                //Do something
            }
        };

        subscribe('/event/ExamplePlatformEvent__e', -1, onMessageCallback).then((response) => {
            console.log('Successfully subscribed to : ', JSON.stringify(response.channel));
        });
    }
}

Now, the latest sfdx-lwc-jest has a stub of lightning/empApi and I can instantiate the above component with a mock implementation in a test.

describe('c-example-component', () => {
    it('test', () => {
        const mockResponse = {
            "id": "_1583742038741_4782",
            "channel": "/event/ExamplePlatformEvent__e",
            "replayId": -1
        }
        const mockEvent = {
            "data": {
                "schema": "a9SbAGsZvysbJq_U77Mv6Q",
                "payload": {
                    "CreatedById": "00556000004PKXTAA4",
                    "CreatedDate": "2020-03-09T14:05:35Z",
                    "SomethingField__c": "ABCDEFG"
                },
                "event": {
                    "replayId": 123
                }
            },
            "channel": "/event/ExamplePlatformEvent__e"
        }

        subscribe.mockImplementation((channel, replayId, onMessageCallback) => {
            onMessageCallback(mockEvent);
            return Promise.resolve(mockResponse);
        });

        const element = createElement('c-example-component', {
                is: ExampleComponent
            });
        document.body.appendChild(element);

        expect(subscribe.mock.calls[0][0]).toBe('/event/ExamplePlatformEvent__e');
        expect(subscribe.mock.calls[0][1]).toBe(-1);
    });
});

In the above test, mock subscribe() is called one time during connectedCallback(). But how to call onMessageCallback again after component is rendered? The parameter isSomething is false for the first time and it will be true by a user operation after component is rendered. So, inside the if clause in onMessageCallback cannot be covered in the current test.

1 Answer 1

4

Too bad this question didn't get an answer earlier. I was able to make this work by using the Module Imports concept under Jest Test Patterns in the LWC Docs. Credit also goes to this StackExchange answer for clarifying how to flush all promises in Node.

Create the following files:

force-app\test\jest-mocks\lightning\empApi.js (this is your custom lightning/empApi stub)

// An object to store callbacks
const _channels = {};

// On subscribe, store the callback function and resolve the promise
export const subscribe = jest.fn((channel, replayId, onMessageCallback) => {
    _channels[channel] = { onMessageCallback };
    return Promise.resolve({
        id: "_" + Date.now(),
        channel: channel,
        replayId: replayId
    });
});

// I'm using isEmpEnabled in my component, so I have it set to return true
export const isEmpEnabled = jest.fn().mockResolvedValue(true);

// A Jest-specific function for "publishing" your Platform Event
export const jestMockPublish = jest.fn((channel, message) => {
    if (
        _channels[channel] &&
        _channels[channel].onMessageCallback instanceof Function
    ) {
        _channels[channel].onMessageCallback(message);
    }
    return Promise.resolve(true);
});

// I just copied these from the standard lightning/empApi stub
export const unsubscribe = jest.fn().mockResolvedValue({});
export const onError = jest.fn().mockResolvedValue(jest.fn());
export const setDebugFlag = jest.fn().mockResolvedValue();

jest.config.js (this overrides the stub from sfdx-lwc-jest and goes at the root of your project)

const { jestConfig } = require("@salesforce/sfdx-lwc-jest/config");
module.exports = {
    ...jestConfig,
    moduleNameMapper: {
        "^lightning/empApi$":
            "<rootDir>/force-app/test/jest-mocks/lightning/empApi"
    }
};

Now, if your component looks like this...

force-app\main\default\lwc\myComponent\myComponent.js

import { LightningElement, api, track } from "lwc";
import {
    subscribe,
    unsubscribe,
    onError,
    isEmpEnabled
} from "lightning/empApi";

export default class MyComponent extends LightningElement {
    @api recordId;
    @track events = [];
    channelName = "/event/My_Platform_Event__e";
    subscription = {};

    async connectedCallback() {
        const empEnabled = await isEmpEnabled();
        if (empEnabled) {
            this.subscription = await subscribe(this.channelName, -2, event => {
                // Your event processing logic here
                console.log(event.data.payload);
            });
        }
    }
}

...you can test it like this...

import { createElement } from "lwc";
import myComponent from "c/myComponent";
import { jestMockPublish } from "lightning/empApi";
// eslint-disable-next-line no-undef
const flushPromises = () => new Promise(setImmediate);

describe("myComponent tests", () => {
    afterEach(() => {
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
    });

    // NOTE -- THIS IS ASYNC
    it("does what I expect", async () => {
        const element = createElement("my-component", {
            is: myComponent
        });
        element.recordId = "OPP001";
        // Add the component to the DOM
        document.body.appendChild(element);

        // Make sure async subscribe call in connectedCallback completes
        await flushPromises();

        // connectedCallback is now complete, but no Platform Events have 
        // been published yet.  Make assertions here about the state of your 
        // component prior to receiving the Platform Events.

        // Mock-publish a Platform Event and await the promise
        await jestMockPublish("/event/My_Platform_Event__e", {
            data: {
                payload: {
                    Opportunity__c: "OPP001",
                    My_Custom_Field__c: 123
                }
            }
        });
        // Now any DOM updates that depend on the Platform Event should 
        // have rendered; assert about them here
    });
});

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .