4

I believe it is a simple fix, but I am new to everything and Jest being the latest one I am exposed to.

I am trying to test if the lightning-combobox received the data to fill its options after the apex imperative call.

I still have no clue what I am doing, I have been copying code from orgs other components The error I receive is on the data being returned, I expect two records to be returned from the mock data, but it received undefined.

Here is the mock data -

[
    {
        "value": "00T2h00000BC9naEAD",
        "label": "Test Task 2 | 2021-08-06 10:00 AM | owner name"
    },
    {
        "value": "00T2h00000BC9naTAS",
        "label": "Test Task 2 | 2021-08-06 10:05 AM | owner name"
    }
]

Here is component HTML

<lightning-combobox class="tasksList" label="Select Task"             
   options={tasks}
   value={defaultTask}
   onchange={handleTaskSelection} required>
</lightning-combobox>

Here is the JS -

connectedCallback() {
    getTasksAssociatedToContact({
        contactRecordId: this.contactRecordId
    }).then(data => {
        if (data) {               
            this.tasks = data.map(item => {
                return {
                    value: item.Id,
                    label: item.Subject + ' | ' + item.CreatedDate + ' | ' + item.Owner.Name,
                }
            })
        }
    })
}

Here is the LWC Jest Test, I am attaching the whole Jest code as I dont know what helps you all to help me -

import { createElement } from 'lwc';
import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
import getTasksAssociatedToContact from "@salesforce/apex/PopulateTasksForContactController.getTasksAssociatedToContact";
import PopulateTasksForContact from 'c/populateTasksForContact';

jest.mock('lightning/flowSupport', () => {
    return 'FlowNavigation';
},
    { virtual: true }
);

const mockData = require('./data/tasksData.json');
const noMockData = require('./data/notasksData.json');
const getTasksAssociatedToContactAdapter = registerApexTestWireAdapter(getTasksAssociatedToContact);

describe('c-populate-tasks-for-contact', () => {
    afterEach(() => {
        // The jsdom instance is shared across test cases in a single file so reset the DOM
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
        // Prevent data saved on mocks from leaking between tests
        jest.clearAllMocks();
    });
    describe('getTasksForContact Apex @imperative call', () => {
        it('renders tasks to combobox if data returned from imperative apex call', () => {
            const element = createElement('c-populate-tasks-for-combobox', {
                is: PopulateTasksForContact
            });
            element.contactRecordId = '0032h00000YxNzIAAV';
            document.body.appendChild(element);

            getTasksAssociatedToContactAdapter.emit(mockData);

            return Promise.resolve().then(() => {
                let combobox = element.shadowRoot.querySelector('.tasksList');
                expect(combobox.length).toBe(2);
            })
        })
    })
});

Please advise.

4
  • Does it verify that it did grab something when getting the component's shadowroot? Also JEST testing is very different. I just spent the last 2 weeks doing these so I feel your pain. youtube vids that helped me Commented Aug 7, 2021 at 2:15
  • 1
    Yes it does, but I was able to make something work for now. But I still don't understand how it worked. Have to work on it more.
    – sumchans
    Commented Aug 7, 2021 at 2:17
  • Could you go ahead and add your solution to your question, that way others can see it. JEST on LWCs don't have that many solutions/examples people could reference. Commented Aug 7, 2021 at 2:19
  • 1
    Sure will do once I am back to my desk.
    – sumchans
    Commented Aug 7, 2021 at 2:21

1 Answer 1

3

lwc-recipes is a good resource for examples of how to mock different LWC scenarios. For your situation, there's an example of apexImperativeMethodWithParams.

In this scenario, you have to do two things:

  1. Mock the imported module (the import statement for your apex method).
  2. Provide data to return for the mocked imperative apex call

Based on what you provided, it seems you have some confusion or have copied from code that is testing wire adapters. You're using emit() which is an API to provide mocked data related to wire services in tests. Write Jest Tests for Lightning Web Components That Use the Wire Service and the trailhead for Write a Jest Test for Wire Service go over this.

The generic adapter emits data on demand when you call the emit() API.

Going back to our #1 and #2 above, it's important to understand some things if you've gotten used to copying and pasting.

  1. jest.mock allows us to mock a whole module
  2. jest.fn creates a mock function

To do #1, you mock the imported module related to your apex method used in the call. In this, you'll see we return jest.fn() which is a new, unused mock function. The virtual: true part is because this mock is for a module that doesn't exist anywhere in the system.

// Mocking imperative Apex method call
jest.mock(
    '@salesforce/apex/PopulateTasksForContactController.getTasksAssociatedToContact',
    () => {
        return {
            default: jest.fn()
        };
    },
    { virtual: true }
);


Now, we need to provide the value to control what is returned in this mocked imperative call for a success. We can do this with mockResolvedValue(). That is syntactic sugar function for:

jest.fn().mockImplementation(() => Promise.resolve(value));

You'll see that this is returning a resolved promise (with the value you provide). You can correlate this within your code as the path that occurs within your .then() branch of your imperative apex call.

connectedCallback() {
    getTasksAssociatedToContact({
        contactRecordId: this.contactRecordId
    }).then(data => {
        //resolved promise - pass mocked data or empty to test different scenarios
        }
    })
}

There is also mockRejectedValue() which is useful for testing the non-happy path if you implemented error handling through .catch()

    getTasksAssociatedToContact({
        contactRecordId: this.contactRecordId
    }).then(data => {

        }
    }). catch((error) => {
        //the rejected promise path
    }).

Knowing all that above, you provide the value like so

getTasksAssociatedToContactAdapter.mockResolvedData(mockData);

One last thing, you seem to have nested describe calls, but the following explains the difference between describe and it. describe breaks your test suite into components and it is where you perform individual tests whose expectation can be expressed in a sentence it lists returned tasks from imperative apex. With that and the above, you get something looking like this:

import { createElement } from 'lwc';
import getTasksAssociatedToContact from "@salesforce/apex/PopulateTasksForContactController.getTasksAssociatedToContact";
import PopulateTasksForContact from 'c/populateTasksForContact';


jest.mock(
'@salesforce/apex/PopulateTasksForContactController.getTasksAssociatedToContact',
    () => {
        return {
            default: jest.fn()
        };
    },
    { virtual: true }
);

describe('c-populate-tasks-for-contact', () => {
    afterEach(() => {
        // The jsdom instance is shared across test cases in a single file so reset the DOM
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
        // Prevent data saved on mocks from leaking between tests
        jest.clearAllMocks();
    });


    it('renders tasks to combobox if data returned from imperative apex call', async () => {

        //assign mock value for resolved apex promise
        getTasksAssociatedToContactAdapter.mockResolvedData(mockData);

        const element = createElement('c-populate-tasks-for-combobox', {
            is: PopulateTasksForContact
        });
        element.contactRecordId = '0032h00000YxNzIAAV';
        document.body.appendChild(element);

            
        return Promise.resolve().then(() => {
            let combobox = element.shadowRoot.querySelector('.tasksList');
                expect(combobox.length).toBe(2);
        });
    });
});

You must log in to answer this question.

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