Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock AWS sqs call for unit testing

I'm using AWS SQS queue in a Node application and I have to write the unit test cases for the same. For that, I want to mock the SQS function call sendMessage() in the test files so, what should I do?

I have tried using aws-sdk-mock but while making the call to the sendMessage(), the function is trying to connect to the Queue URL.

Test File

import AWSMock from 'aws-sdk-mock'
import sendMessage from '../api/sqs/producer'

describe.only('Test case for SQS SendMessage', () => {
  it('should return the UserEvent', async () => {
    AWSMock.mock('SQS', 'sendMessage', () => Promise.resolve('Success'))
    const res = await sendMessage('testURL', 'data')
    console.log('RES', res.response.data)
  })
})

Producer File

const AWS = require('aws-sdk')

const sqs = new AWS.SQS({
  region: 'us-east-1'
})

const sendMessage = async (msg, queueUrl) => {
  try {
    const params = {
      MessageBody: JSON.stringify(msg),
      QueueUrl: queueUrl
    }
    const res = await sqs.sendMessage(params).promise()
    return res
  } catch (err) {
    console.log('Error:', `failed to send message ${err}`)
    throw new Error(err)
  }
}

export { sendMessage as default }

In above code, I'm expecting the Success as a return value in res

Output

 FAIL  tests/sendMessage.test.js
  ● Console

    console.log api/sqs/producer/index.js:16
      Error: failed to send message UnknownEndpoint: Inaccessible host: `testurl'. This service may not b
e available in the `us-east-1' region.

  ● Test case for SQS SendMessage › should return the UserEvent

    UnknownEndpoint: Inaccessible host: `testurl'. This service may not be available in the `us-east-1' r
egion.
like image 678
FLASH Avatar asked Aug 21 '19 06:08

FLASH


People also ask

What is AWS SDK mock?

The mocking library for AWS SDK for JavaScript (v3) can be used in any JavaScript unit testing framework. Internally, it uses Sinon. JS to create robust stubs in place of the SDK calls. You can install it with your favorite package manager.

Can SQS call API?

You can use the AWS Javascript API in your JS app, and then use that library to make the SQS API call for you.

How do you call a postman from the SQS?

Adding a message to standard SQS queue using Postman Open Postman and paste the URL of the queue in a new window. To add a new message to the queue, we will add the following parameters: Action=SendMessage. MessageBody=TEST.


3 Answers

Here is the solution, you don't need aws-sdk-mock module, you can mock aws-sdk by yourself.

index.ts:

import AWS from 'aws-sdk';

const sqs = new AWS.SQS({
  region: 'us-east-1'
});

const sendMessage = async (msg, queueUrl) => {
  try {
    const params = {
      MessageBody: JSON.stringify(msg),
      QueueUrl: queueUrl
    };
    const res = await sqs.sendMessage(params).promise();
    return res;
  } catch (err) {
    console.log('Error:', `failed to send message ${err}`);
    throw new Error(err);
  }
};

export { sendMessage as default };

index.spec.ts:

import sendMessage from './';
import AWS from 'aws-sdk';

jest.mock('aws-sdk', () => {
  const SQSMocked = {
    sendMessage: jest.fn().mockReturnThis(),
    promise: jest.fn()
  };
  return {
    SQS: jest.fn(() => SQSMocked)
  };
});

const sqs = new AWS.SQS({
  region: 'us-east-1'
});

describe.only('Test case for SQS SendMessage', () => {
  beforeEach(() => {
    (sqs.sendMessage().promise as jest.MockedFunction<any>).mockReset();
  });
  it('should return the UserEvent', async () => {
    expect(jest.isMockFunction(sqs.sendMessage)).toBeTruthy();
    expect(jest.isMockFunction(sqs.sendMessage().promise)).toBeTruthy();
    (sqs.sendMessage().promise as jest.MockedFunction<any>).mockResolvedValueOnce('mocked data');
    const actualValue = await sendMessage('testURL', 'data');
    expect(actualValue).toEqual('mocked data');
    expect(sqs.sendMessage).toBeCalledWith({ MessageBody: '"testURL"', QueueUrl: 'data' });
    expect(sqs.sendMessage().promise).toBeCalledTimes(1);
  });

  it('should throw an error when send message error', async () => {
    const sendMessageErrorMessage = 'network error';
    (sqs.sendMessage().promise as jest.MockedFunction<any>).mockRejectedValueOnce(sendMessageErrorMessage);
    await expect(sendMessage('testURL', 'data')).rejects.toThrowError(new Error(sendMessageErrorMessage));
    expect(sqs.sendMessage).toBeCalledWith({ MessageBody: '"testURL"', QueueUrl: 'data' });
    expect(sqs.sendMessage().promise).toBeCalledTimes(1);
  });
});

Unit test result with 100% coverage:

 PASS  src/stackoverflow/57585620/index.spec.ts
  Test case for SQS SendMessage
    ✓ should return the UserEvent (7ms)
    ✓ should throw an error when send message error (6ms)

  console.log src/stackoverflow/57585620/index.ts:3137
    Error: failed to send message network error

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 index.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.453s, estimated 6s

Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57585620

like image 120
slideshowp2 Avatar answered Nov 03 '22 01:11

slideshowp2


The problem here is that the SQS service is initialized outside of the handler, ergo at the time the module is requested. As a result, the mock call will happen too late, as the service to be mocked (SQS in this case) was already created.

From the docs:

NB: The AWS Service needs to be initialised inside the function being tested in order for the SDK method to be mocked

Updating your producer file as follows will correctly work with aws-sdk-mock:

const AWS = require('aws-sdk')

let sqs;

const sendMessage = async (msg, queueUrl) => {
  if(!sqs) {
    sqs = new AWS.SQS({
      region: 'us-east-1'
    });
  }
  try {
    const params = {
      MessageBody: JSON.stringify(msg),
      QueueUrl: queueUrl
    }
    const res = await sqs.sendMessage(params).promise()
    return res
  } catch (err) {
    console.log('Error:', `failed to send message ${err}`)
    throw new Error(err)
  }
}

export { sendMessage as default }
like image 35
thomaux Avatar answered Nov 02 '22 23:11

thomaux


If you have a static sqs test message (for example in a unittest situation where you do hit sqs for some unavoidable reason), you could calculate the md5 sum by simply running the sendMessage against an actual SQS queue (make one quickly in some burner AWS Account, then log the response and md5sum the MessageBody object in the response.

In your unittest, you can then nock SQS simply by using

    const requestId = 'who';
    const messageId = 'wha';
    nock('https://sqs.eu-central-1.amazonaws.com')
        .post('/')
        .reply(
            200,
            `<SendMessageResponse><SendMessageResult><MD5OfMessageBody>193816d2f70f3e15a09037a5fded52f6</MD5OfMessageBody><MessageId>${messageId}</MessageId></SendMessageResult><ResponseMetadata><RequestId>${requestId}</RequestId></ResponseMetadata></SendMessageResponse>`,
        );

Do not forget to change your region and ofcourse the md5sum ;)

This method does not scale obviously, unless you calculate the messageBody's md5sum up front :)

Maybe it can help some folks with static unittest messages towards a quick fix.

like image 33
gbvanrenswoude Avatar answered Nov 02 '22 23:11

gbvanrenswoude