Mocking – Testing Serverless Applications

Mocking

Unit tests should be predictable. In other words, a unit test should produce the same result every time it is executed with the same input.

Take the addNumbers method shown here:

export const addNumbers = (numbers) => {

return numbers.reduce((a, b) => {

return a + b;

});

};

export const handler = async (event) => {

return addNumbers(event.numbers);

};

This method can be unit tested, as any assertions will always produce the same results:

import { addNumbers } from “./”;

test(“Should calculate 1+2+3+4=10”, () => {

const actual = addNumbers([1, 2, 3, 4]);

const expected = 10;

expect(actual).toEqual(expected);

});

Any nontrivial Lambda function will usually contain side effects, such as network requests to third-party vendors or AWS SDK calls. These side effects are inherently unpredictable—a side effect could rely on a network connection to the public inter‐ net, for example. The computed result of a side effect may also depend upon a third-party implementation that is subject to change.

To keep unit tests predictable (and fast), side effects must be mocked.

A popular criticism of mocking (or “stubbing”) parts of a system under test is that it is not a true test of the system. This is certainly a valid criticism, but only if your aim is to properly replicate the whole system in order to verify its quality and adherence to its requirements.

You have already begun to see why testing a serverless system as a whole may not be the optimum strategy. Identifying the parts of your Lambda functions to mock is usually a byproduct of drawing the boundaries of responsibility between you and your vendors. It is a sensible strategy to mock any code that you are not responsible for testing or fixing.

Mocking is an essential tool when testing serverless microservices, but it is not without its pitfalls. The problem with mocking comes at scale. Your tests will scale with a lot less friction if you isolate mocks to individual tests and units under test rather than mocking once on a global level.

The following example uses the JavaScript AWS SDK to put an event on an Event‐

Bridge event bus:

import { EventBridgeClient, PutEventsCommand } from “@aws-sdk/client-eventbridge”;

export const eventbridgeClient = new EventBridgeClient();

export const handler = async () => {

const putEventsCommand = new PutEventsCommand({ Entries: [

{

Detail: JSON.stringify({ “order”: “1234” }),

DetailType: “OrderCreated”,

EventBusName: “order-bus”,

Source: “service.order”,

},

],

});

await eventbridgeClient.send(putEventsCommand);

return;

};

The send method on the EventBridge client can be mocked in the unit test. When the Lambda function handler is called, the SDK request won’t be made. Instead, a spy can be attached to the mock, allowing you to assert that the SDK request will be made with specific parameters:

test(“Should put event”, async () => {

const putEventsCommandSpy = jest

.spyOn(eventbridgeClient, “send”)

.mockImplementation(() => {});

await handler();

expect(putEventsCommandSpy).toHaveBeenCalledTimes(1);

expect(putEventsCommandSpy).toHaveBeenCalledWith( expect.objectContaining({

input: {

Entries: [

{

Detail: ‘{“orderId”:”1234″}’,

DetailType: “OrderCreated”,

Source: “service.order”,

},

],

},

})

);

});

Leave a Reply

Your email address will not be published. Required fields are marked *