SNS_jest

Outline

There are a lot of jest mock examples out there, but never a clear example about Jest with AWS services (in this case, SNS). Today, in this article I will show you how to mock sns.publish with Jest and with all the code layed out and the logic explained.

  1. Understand what you’re testing
  2. How to write mock for SNS using Jest
  3. Closer look at sns.publish.mock

1. Understand what you’re testing

Let’s take a look at the Node.js code that we want to write our unit test for. Inside sendMessage.js is the handler() function for the AWS Lamda that is responsible to send message to our AWS SNS whenever triggered. How this Lambda function gets triggered doesn’t matter here. All it matters is that this Lambda function will send message to our target SNS.

Inside sendMessage.js:

const AWS = require('aws-sdk')
const sns = new AWS.SNS()

// this handler sends message to SNS whenever called upon
const handler = async event => {
    if event.status === "send" {
        try {
            await sns.publish({
                Message: "send this message to SNS..."
            }).promise()
        } catch (err) {
            throw err
        }
    }
}

module.exports = {
    handler
}

This is a fairly basic example, but enough to help us demonstrate how to mock sns.publish.

Inside handler() function, it takes in an input event and checks if event.status equals to “send”. If equals to “send” then send the hard coded message “send this message to SNS…” to our target SNS.

Now, let’s try and mock this Lambda handler() function that publishes message to SNS!

2. How to write mock for sns.publish using Jest

We will be using Jest to write our unit test and that’s it. No fancy thrid party packages needed. First, you need to create a new file named sendMessage.test.js that will contain our tests.

Inside sendMessage.test.js:

describe('sendMessage handler behaviors', () => {
    let mockSNS

    beforeAll(() => {
        // things you want to do before all the tests starts
    })

    afterAll(() => {
        // things you want to do after all the tests ends
    })

    beforeEach(() => {
        jest.resetModules()

        const AWS = require('aws-sdk')
        mockSNS = {
            publish: jest.fn().mockReturnValue({
                promise: jest.fn().mockResolvedValue({})
            })
        }
        jest.spyOn(AWS, 'SNS').mockReturnValue(mockSNS)
    })

    test('should send message to SNS', async () => {
        const handler = require('./sendMessage')
        await handler(triggerInput("send"))
        expect(mockSNS.publish).toHaveBeenCalledTimes(1)
        expect(mockSNS.publish).toHaveBeenCalledWith(expectedPublishContent)
    })

    const triggerInput = (status) => {
        status: status
    }

    const expectedPublishContent = () => {
        Message: "send this message to SNS..."
    }
})

Ok, let’s break down this unit test code:

  • describe(): the testing function that we use to define our test cases and behaviors that will be carried out at different stages. This is not a required step, you can skip describe() and write test() instead. But when there are a lot of tests, it would be best to organize your test cases with describe().

  • beforeAll(): define things you want to do before all the tests starts.

  • afterAll(): define things you want to do after all the tests ends.

  • beforeEach(): define things to do before each test() starts.

    • jest.resetModules(): this resets the module registry that stores cache. This step is useful when you want to make sure that each time your test starts with a clean state.

    • jest.spyOn(AWS, 'SNS'): creates a mock function like jest.fn that tracks calls coming into AWS.SNS(), and returns a mock function. Everytime before each test case, we will mock AWS.SNS() with this spyOn function. Also, remember that the const AWS = require('aws-sdk') should be defined inside beforeEach(), because we want a new AWS for each test case and not a global one (don’t require aws-sdk module outside of beforeEach()).

    • .mockReturnValue(mockSNS): meaning that our mock function created with jest.spyOn(AWS, 'SNS') will return mockSNS it’s return value.

  • test(): the specific test case that we want the describe() function to run. Inside test('should send message to SNS) , it will require sendMessage.js and call the handler() that was previously defined.

    • expect(mockSNS.publish).toHaveBeenCalledTimes(1): This checks if our mocked sns.publish was called once. If called once, then retirn true. In our case, it should be true, since handler(triggerInput("send")) does receice an event with event.status equals to “send”, and so our lambda function should sns.publish the message we want.

    • expect(mockSNS.publish).toHaveBeenCalledWith(expectedPublishContent): this checks if the correct message was called. This basically checks if the correct message was send through sns.publish. In this case, the correct message is defined inside expectedPublishContent. This expect function will check if the message we send to mock sns is the same as what’s inside expectedPublishContent. If the same, then return true.

3. Closer look at mockSNS.publish.mock

Every mock function in Jest have a .mock property that stores the information about how the mock function was called. The same goes with our mockSNS.publish. You must be really curious about what is actually inside mockSNS.publish.mock right? Well, below is an example of the object inside mockSNS.publish.mock after running one of the test and the call have been catched by our jest.SpyOn function.

# console.log(mockSNS.publish.mock)
{
    "calls": [
        [
            {
                "Message": "send this message to SNS..."
            }
        ]
    ],
    "instances": [
        {}
    ],
    "invocationCallOrder": [
        1
    ],
    "results": [
        {
            "type": "return",
            "value": {}
        }
    ]
}
  • calls: an array of all the calls this mock function received

    • calls[0]: first call to the mock function

    • calls[0][0]: first arg of the first call of the mock function

  • instances: an array of objects returned by each instantiation of the mock function

    • instances[0]: the first instantiation of our mock function

    • In this case, our returned object when instantiating the mock function is empty.

  • invocationCallOrder: an array of the order of each call made to the mock function.

    • invocationCallOrder[0]: is the first call’s call order

    • Our call order is 1, because our first and only call was the first to call the mock function.

  • results: an array of the return values by the mock function when each call was called

    • results[0]: return value from the first call

    • In our case, the return value for the first call is an empty object.

Resources