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.
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.
You can use the AWS Javascript API in your JS app, and then use that library to make the SQS API call for you.
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.
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
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 }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With