Giter Site home page Giter Site logo

salesforce / wire-service-jest-util Goto Github PK

View Code? Open in Web Editor NEW
20.0 19.0 15.0 1.39 MB

Utility library for @-wire Lightning Web Component tests

License: MIT License

JavaScript 76.93% HTML 1.20% TypeScript 21.87%
salesforce lightning-platform wire stream test

wire-service-jest-util's Introduction

@salesforce/wire-service-jest-util

A utility so Lightning Web Component unit tests can control the data provisioned with @wire.

Basic Example

Assume you have a component like this.

import { LightningElement, wire } from 'lwc';
import { getTodo } from 'x/todoApi';
export default class MyComponent extends LightningElement {
    @wire(getTodo, {id: 1})
    todo
}

You'd like to test the component's handling of @wire data and errors. This test utility makes it trivial.

Create a mock/stub of the x/todoApi module, and use createTestWireAdapter from @salesforce/wire-service-jest-util to create a test wire adapter, getTodo. Note: to create a module mock/stub in Jest, check the documentation for Manual mocks, ES6 class mocks and moduleNameMapper configuration.

import { createTestWireAdapter } from '@salesforce/wire-service-jest-util';

export const getTodo = createTestWireAdapter();

Then in your test, import the adapter to emit the data:

import { createElement } from 'lwc';
import MyComponent from 'x/myComponent';

// Import the adapter used by the component under test.
// This will be TestWireAdapter defined in the mocked module. 
import { getTodo } from 'x/todoApi';

describe('@wire demonstration test', () => {

   // disconnect the component to reset the adapter. it is also
   // a best practice to cleanup after each test.
   afterEach(() => {
       while (document.body.firstChild) {
           document.body.removeChild(document.body.firstChild);
       }
   });

   it('handles receiving data', () => {
       // arrange: insert component, with @wire(getTodo), into DOM
       const LightningElement = createElement('x-my-component', { is: MyComponent });
       document.body.appendChild(LightningElement);

       // act: have @wire(getTodo) provision a value
       const data = { 'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': false };
       getTodo.emit(data);

       // assert: verify component behavior having received @wire(getTodo)
   });
});

Overview

This library provides three utility methods to create test wire adapters used in the tests to emit data and get the last resolved @wire configuration. This library doesn’t inject wire adapters for you, and your test configuration has to reroute all the wire adapters imports to resolve a mocked implementation.

Adapter Types

There are three flavors of test adapters: Lightning Data Service (LDS), Apex, and generic. All allow test authors to emit data through the wire. The main difference is that the LDS and Apex wire adapters follow certain patterns that are automatically handled by the test adapters. These patterns include the shape in which data and errors are emitted, and an initial object emitted during registration. The generic test adapter directly emits any data passed to it. See the API section below for more details.

API

createTestWireAdapter

/**
 * Returns a generic wire adapter for the given identifier. Emitted values may be of
 * any shape.
 *
 * @param {Function} This parameter might be used to implement adapters that can be invoked imperatively (like the Apex one).
 */
createTestWireAdapter(identifier: Function): TestWireAdapter;

interface TestWireAdapter {
    /**
     * Emits any value of any shape.
     * @param value The value to emit to the component
     * @param filterFn When provided, it will be invoked for every adapter instance on the
     *                 component with its associated config; if it returns true, the value will be
     *                 emitted to that particular instance.
     */
    emit(value: object, filterFn?: (config) => boolean): void;

    /**
     * Gets the last resolved config. Useful if component @wire uses includes
     * dynamic parameters.
     */
    getLastConfig(): object
}

createLdsTestWireAdapter

/**
 * Returns a wire adapter mock that mimics Lightning Data Service (LDS) adapters behavior,
 * emitted data and error shapes. For example, the emitted shape is
 * `{ data: object|undefined, error: FetchResponse|undefined}`.
 *
 * @param {Function} This parameter might be used to implement adapters that can be invoked imperatively (like the Apex one).
 */
createLdsTestWireAdapter(fn: Function): LdsTestWireAdapter;

interface LdsTestWireAdapter {
    /**
     * Emits data.
     * @param value The data to emit to the component
     * @param filterFn When provided, it will be invoked for every adapter instance on the
     *                 component with its associated config; if it returns true, the data will be
     *                 emitted to that particular instance.
     */
    emit(value: object, filterFn?: (config) => boolean): void;

    /**
     * Emits an error. By default this will emit a resource not found error.
     *
     * `{
     *       ok: false,
     *       status: 404,
     *       statusText: "NOT_FOUND",
     *       body: [{
     *           errorCode: "NOT_FOUND",
     *           message: "The requested resource does not exist",
     *       }]
     *  }`
     */
    error(body?: any, status?: number, statusText?: string): void;

    /**
     * Emits an error. By default this will emit a resource not found error.
     *
     * @param errorOptions
     * @param filterFn     When provided, it will be invoked for every adapter instance on the
     *                     component with its associated config; if it returns true, the error will be
     *                     emitted to that particular instance.
     */
    emitError(
        errorOptions?: { body?: any, status?: number, statusText?: string },
        filterFn?: (config) => boolean
    ): void;

    /**
     * Gets the last resolved config. Useful if component @wire uses includes
     * dynamic parameters.
     */
    getLastConfig(): object;
}

interface FetchResponse {
    body: any,
    ok: false,
    status: number,
    statusText: string,
}

createApexTestWireAdapter

/**
 * Returns a wire adapter that connects to an Apex method and provides APIs
 * to emit data and errors in the expected shape. For example, the emitted shape
 * is `{ data: object|undefined, error: FetchResponse|undefined}`.
 *
 * @param {Function} An apex adapters are also callable, this function will be called
 *                   when the wire adapter is invoked imperatively.
 */
createApexTestWireAdapter(fn: Function): ApexTestWireAdapter;

interface ApexTestWireAdapter {
    /**
     * Emits data.
     * @param value The data to emit to the component
     * @param filterFn When provided, it will be invoked for every adapter instance on the
     *                 component with its associated config; if it returns true, the data will be
     *                 emitted to that particular instance.
     */
    emit(value: object, filterFn?: (config) => boolean): void;

    /**
     * Emits an error. By default this will emit a resource not found error.
     *
     * `{
     *       ok: false,
     *       status: 400,
     *       statusText: "Bad Request",
     *       body: {
     *           message: "An internal server error has occurred",
     *       }
     *  }`
     */
    error(body?: any, status?: number, statusText?: string): void;

    /**
     * Emits an error. By default this will emit a resource not found error.
     *
     * @param errorOptions
     * @param filterFn     When provided, it will be invoked for every adapter instance on the
     *                     component with its associated config; if it returns true, the error will be
     *                     emitted to that particular instance.
     */
    emitError(
        errorOptions?: { body?: any, status?: number, statusText?: string },
        filterFn?: (config) => boolean
    ): void;

    /**
     * Gets the last resolved config. Useful if component @wire uses includes
     * dynamic parameters.
     */
    getLastConfig(): object;
}

interface FetchResponse {
    body: any,
    ok: false,
    status: number,
    statusText: string,
}

wire-service-jest-util's People

Contributors

abdulsattar avatar dependabot[bot] avatar diervo avatar ekashida avatar jbleylesf avatar jodarove avatar jye-sf avatar kevinv11n avatar nolanlawson avatar pmdartus avatar ravijayaramappa avatar svc-scm avatar wjhsf avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wire-service-jest-util's Issues

Apex test wire adapter's emit() method should correctly shape data

It appears that the emit method of the apex test wire adapter is no longer using the correct shape ({ data: undefined, error: err })

If I have a LWC with the following code:

@wire(getFoo)
wiredGetFoo(result) {
    const { data } = result;
    if (data) {
        this.bar = data.map((bar) => ({ ...bar }));
    }
}

And a jest test that mocks the wire adapter and emits the contents of a .json file:

import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
import getFoo from '@salesforce/apex/FooController.getFoo';
const getFooWireAdapter = registerApexTestWireAdapter(getFoo);
const mockGetFoo = require('./data/getFoo.json');
...
getFooWireAdapter.emit(mockGetFoo);

We find that result.data is undefined. This started happening a few nights ago, which would seem to correspond with this commit https://github.com/salesforce/wire-service-jest-util/commit/d363073d3735b17bd5659cf00ce35cba9ea1f70d.

To verify this was the issue, I wrapped the contents of the .json file in a "data" attribute and verified that the test passed (as it had previously).

Issue with getObjectInfos wired method

I have been using the "registerLdsTestWireAdapter" for mocking wired methods in my lightning web components and it has been working great. However, I tried to do the same with the "getObjectInfos" (from lightning/uiObjectInfoApi) wired method, it gives an error saying "No adapter specified". Is this wired method not supported?

@salesforce/wire-service-jest-util is throwing error, missing declaration file

Could not find a declaration file for module '@salesforce/wire-service-jest-util'. 'd:/LWCSessions/Telstra_Dev09/node_modules/@salesforce/wire-service-jest-util/dist/wire-service-jest-util.common.js' implicitly has an 'any' type.
Try npm install @types/salesforce__wire-service-jest-util if it exists or add a new declaration (.d.ts) file containing declare module '@salesforce/wire-service-jest-util';

Could anyone please guide, how to add a new declaration file.

createLDSTestWireAdapter does not work with GraphQL wire adapter

The new LDS GraphQL wire adapter emits data in the shape { data: ..., errors: [ ... ] }. This prevents component authors from using createLDSTestWireAdapter, which assumes the standard LDS { data: ..., error: ... } shape for emitted values.

Proposal is to create a new createGraphQLTestWireAdapter function & corresponding class in src/adapters that understands both the different shape & the special format of GraphQL errors and provides utility methods for constructing values to emit.

The alternative is to have test authors use createTestWireAdapter and create the GraphQL shapes themselves.

What is the equivalent of getRecordAdapter.error() ?

Since registerLdsTestWireAdapter is deprecated, what is the equivalent of getRecordAdapter.error();

import { getRecord } from 'lightning/uiRecordApi';
import { registerLdsTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
const getRecordAdapter = registerLdsTestWireAdapter(getRecord);

...

 getRecordAdapter.error();
...

I can't do getRecord.emit() to resolve into error because it is only used to return the data key. So, what should I do? Or if it is possible, could you give an example of how I make it return the mock data into the error key?

registerApexTestWireAdapter is not emitting data and unit testing are failing as a result

I have following code for LWC unit test

import { createElement } from 'lwc';
import MyComponent from 'c/myComponent';
import getData '@salesforce/apex/FormController.getData';
import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest';

const mockRecords = require('./data/getRecords.json');
const getDataAdapter = registerApexTestWireAdapter(getData);

describe('c-my-component', () => {
    afterEach(() => {
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }

        jest.clearAllMocks();
    });

    it('renders component based on data', () => {
        const element = createElement('c-my-component', {
            is: MyComponent 
        });
        document.body.appendChild(element);

        getDataAdapter.emit(mockRecords);

        return Promise.resolve().then(() => {
            const listEls = element.shadowRoot.querySelectorAll(
                '.list-element'
            );

            expect(listEls.length).toBe(mockRecords.length);
       });
    });

This unit test is failing because the apex test wire adaptor is not emitting data and the test component is not rendering list in jsdom.

In my application the "@salesforce/wire-service-jest-util" version was 2.4.0 in package-lock.json, However when I downgraded the version to 2.3.0 the wire adaptor was emitting data properly and unit test cases were working fine.

Is this an known issue with @salesforce/wire-service-jest-util version 2.4.0, Is there any way by which we can use ApexTestWireAdaptor for emitting data while using wire-service-jest-util 2.4.0 ?

Improvement: <adapter>.emit() should return a promise indicating when task is complete

I'm not sure this is even possible, but...

We (Platform Actions) have now encountered the second instance where the effects of calling .emit() were not complete by the time the Jest test proceeded. For example, we have the following in lwcQuickActionLayout.test.js:

import { registerLdsTestWireAdapter } from 'wire-service-jest-util';
import { getQuickActionRecordLayoutTemplate } from 'laf/templateApi';
const getQALTemplateAdapter = registerLdsTestWireAdapter(getQuickActionRecordLayoutTemplate);

[...]

   it('passes the transformed actionApiName to the QAL template wire', async () => {
        expect.assertions(2);
        const el = createComponentUnderTest(inputs);
        expect(el).toBeDefined();

        getQALTemplateAdapter.emit({ data: {} });

        await Promise.resolve();
        const lastConfig = getQALTemplateAdapter.getLastConfig();
        expect(lastConfig.actionApiName).toEqual("Global_LogACall");
    });

Locally, this test succeeds as expected; the wire and its effects are processed before const lastConfig = getQALTemplateAdapter.getLastConfig();.

On autobuilds, this fails. It appears that the internal implementation of the wire system does not succeed before the expectations of the test are processed. I'd argue that even the await Promise.resolve() is unnecessary but was probably added by the test author to get this to succeed locally.

As another example, we have this in our codebase now:

    it('renders standard buttons on the server-side', async () => {
        expect.assertions(3);

        const mockActionsReceivedListener = jest.fn((e) => {
            expect(e).toHaveProperty('detail');
            expect(e.detail.actions[0].render.useServerSideRendering).toBe(true);
        });
        const listeners = {};
        listeners[ACTIONS_RECEIVED_EVENT] = mockActionsReceivedListener;
        createComponentUnderTest({}, listeners);

        // Force microtask
        await Promise.resolve();
        const action = Object.assign({}, mockActions[0]);
        action.type = "StandardButton"; // It should already be a standard button, but double-check!
        getRecordActionsAdapter.emit({"actions": {"003xx000004WhCbAAK": {"actions": [action]}}});

        // Wait to allow time for the event to propagate
        // W-6543284: The test is passing locally but failing in autobuilds. I suspect this is due to a timing issue.
        // Introducing a timeout to give the event time to propagate
        // TODO: Revisit this to lower the timeout if possible.
        await (new Promise((resolve) => {
            // eslint-disable-next-line @lwc/lwc/no-async-operation
            setTimeout(resolve, 100);
        }));
        expect(mockActionsReceivedListener).toHaveBeenCalledTimes(1);
    });

The block between the comment referencing W-6543284 and the expect() call was added to get around the same kind of result: the effects of getRecordActionsAdapter.emit() did not complete before the next lines were processed.

In my opinion, these hacks should not be necessary in individual tests. Is it possible to improve the implementations of these wire adapter mocks to properly abstract away the internal implementation? Ideally this would be achieved by returning a promise one could await to know that the effects have been completed.

Test wire adapters should filter based on config

The current logic in the test adapter emit functions sends data to all matching wire adapters, regardless of the config of the wire adapter.

This is a problem if there are multiple wire adapters connected with different configs since there is no way to emit data to one adapter but not the other.

In the example below, this tool provides no way to emit data to only one of the @wire functions.

@wire(getRecord, { recordId: '$recordId', ['Account.Name']})
accountData(data, error){
    // do something with an account
}

@wire(getRecord, { recordId: '$recordId', ['Contact.Name']})
contactData({data}){
    // do something with a contact
}

Give the ability to reset the last config

The TestWireAdapterTemplate exposes a getLastConfig method but no way to reset it.

So for some tests where we want to ensure the wire was not called for some functional reasons, we should add them as first tests into the test file and ensure the wire is not called first on another file.

Ideally we should be able to give an easy way to reset any last config (classic, Lds, Apex) without hacking the internal implementation (we could set to null the static field _lastConfig but it means we know the internal implementation)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.