5

I'm going to do a Q&A-style entry here, since I couldn't find anything on the web describing this use case. I have seen several threads here describing how to do an empty mock on an Apex call in LWC using Jest, like this:

import insertRecords from '@salesforce/apex/MyController.insertRecords';

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

I want my mock Apex call to return a value. In this example, I'm inserting records and returning those inserted records. In order to fully test my component, the Apex stub needs to return the same array of objects it received, but with the Id property populated.

3

For easier readability, you can store as many .json files as needed for all your mocked data needs and have "less" in your jest test.

Within your __tests__ directory, you can create a data folder and then store the .json file of your mocked data within it. File structure would look like so:

force-app
└───main
    └───default
        └───lwc
            └───lwcComponentName
                └───__tests__
                    |   lwcComponentName.test.js
                    └───data
                        |   insertedRecordsMock.json
                        |   insertedRecordsErrorMock.json

Your .json file would just be the response you get from your call. This is helpful for responses you want to mock that may be large

{
  "apiName": "Account",
  "fields" : {
    "Name": "Local Boxes",
    "BillingState": "WA",
    "BillingStreet" : "123 Main Street",
    "BillingCountry" : "USA"
  }
}

Within a given jest test, you'll have all the imports needed at the top (in this example, your apex method you call imperatively).

import insertRecords from '@salesforce/apex/MyController.insertRecords';

Load as many files in separate constants as you need for all your tests. In this example, we have mock data for a happy path and failure path

const mockInsertedRecords = require("./data/insertedRecordsMock.json");
const mockErrorInsertedRecords = require("./data/insertedRecordsErrorMock.json");

Mock the imperative apex call - we return a new, unused mock function so we can pick what the return will look like within the specific test.

jest.mock(
    "@salesforce/apex/MyController.insertRecords",
    () => {
        return {
            default: jest.fn(),
        };
    },
    { virtual: true }
};

Then, in your actual specific tests - supply whatever data that should be returned for the mock of your call depending on your testing scenario. Below is the happy path as mockResolvedValue really just mocks the Promise.resolve() which is the happy path of your apex imperative call.

insertedRecords.mockResolvedValue(mockInsertedRecords);

For the failure scenario, you want to mock what happens if your promise is rejected so we can utilize mockRejectedValue()

insertedRecords.mockRejectedValue(mockErrorInsertedRecords);
1
  • 1
    Thanks for clarifying the use of mockResolvedValue, mockRejectedValue, and imported JSON files in tests. At the time I originally posted this, these patterns were not well documented in LWC contexts. Future readers will benefit from this info. Aug 26 '21 at 15:30
1

This is how I did it:

jest.mock(
    "@salesforce/apex/MyController.insertRecords",
    () => {
        return {
            default: jest.fn(payload => {
                const response = payload.records.map((record, index) => {
                    return Object.assign({ Id: "TEST_ID" + index }, record);
                });
                return response;
            })
        };
    },
    { virtual: true }
);

In your test, you can now make statements like this:

expect(insertRecords).toHaveBeenCalledWith({ 
    records: [
        { Name: "Record 1" },
        { Name: "Record 2" }
    ]
});
expect(insertRecords).toHaveReturnedWith([
    { Id: "TEST_ID0", Name: "Record 1" },
    { Id: "TEST_ID1", Name: "Record 2" }
]);

To test Apex returning an error, in your test you can add code like this ahead of invoking the action where your Apex method gets called:

insertRecords.mockRejectedValue({
    body: {
        exceptionType: "System.QueryException",
        isUserDefinedException: false,
        message: "List has no rows for assignment to SObject",
        stackTrace:
            "Class.MyController.insertRecords: line 10, column 1"
    },
    headers: {},
    status: 500,
    ok: false,
    statusText: "Server Error"
});

Pitfalls to avoid:

  • If you modify the payload object when setting up the mock, expect(foo).toHaveBeenCalledWith(bar) won't work -- you are actually modifying the payload Jest believes it received.
  • There is a finite list of whitelisted JS objects you're allowed to use inside jest.mock(). I tried to use the spread operator instead of Object.assign(), and it threw an error.
2
  • How would you do a negative case for this? So instead of receiving data you get back an error. IE the JS looking like .then(data => {}) .catch(error => {}) but having it return the error instead Aug 3 '21 at 23:25
  • 1
    Added some guidance for how to mock errors from Apex. Hope it helps. Aug 4 '21 at 21:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.