FLASH
FLASH

Reputation: 1301

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.

Upvotes: 20

Views: 42803

Answers (4)

thomaux
thomaux

Reputation: 19738

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 }

Upvotes: 2

Fernando Bittencourt
Fernando Bittencourt

Reputation: 79

You can take a look at LocalStack. It's a lib that runs on docker and can simulate many AWS services running locally, including SQS.

Upvotes: 0

HgnX
HgnX

Reputation: 29

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.

Upvotes: 1

Lin Du
Lin Du

Reputation: 102587

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

Upvotes: 37

Related Questions