Reputation: 315
Let's assume that I have a QueueClass
with a method send
, that gets some data as parameter
which then sends to a SQS queue.
I want to write 2 tests:
One that tests that the MessageBody and QueueUrl keys have the expected values passed in.
One that in case of error an exception gets thrown.
The method to be tested looks like this:
My send
method:
async send(data) {
return SQS.sendMessage({
MessageBody: JSON.stringify(data),
QueueUrl: 'queue_url_here',
})
.promise()
.catch((error) => {
// Throw exception ...
});
}
The test I have for that method:
const aws = require('aws-sdk');
jest.mock('aws-sdk', () => {
const SQSMocked = {
sendMessage: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return {
SQS: jest.fn(() => SQSMocked),
};
});
sqs = new aws.SQS();
test('my test', async () => {
const data = {};
await QueueClass.send(data);
expect(sqs.sendMessage).toHaveBeenCalledWith({
MessageBody: JSON.stringify(data),
QueueUrl: 'queue_url_here',
});
});
That test gives me the following error:
TypeError: Cannot read property 'catch' of undefined
I did try adding catch: jest.fn()
to the SQSMocked object, the exact same way I do with promise
, but kept getting the same error.
The thing is that when I change the method that I am trying to test so it uses try-catch block
instead of .promise()
and .catch()
:
async send(data) {
try {
return SQS.sendMessage({
MessageBody: JSON.stringify(data),
QueueUrl: 'queue_url_here',
});
} catch (error) {
// Throw exception ...
}
}
my test passes, so that makes me think that this is not necessarily an issue about properly mocking the SQS.
Any ideas why when using .promise()
and .catch()
my test fails ?
Also how could I test a case where an Error gets thrown by the queue ?
I would like to be able to do something like this:
await expect(sqs.sendMessage)
.resolves
.toEqual(...);
OR
await expect(sqs.sendMessage)
.rejects
.toThrowError(new Error('Some error thrown.'));
Upvotes: 1
Views: 4469
Reputation: 349
duplicate questions How to mock AWS sqs call for unit testing
just add the method around to be called like so...
class EventService {
static async sendFifoMessage(
url,
message,
groupId,
dedupeId,
) {
const sqsMessageRequest = {
QueueUrl: url,
MessageBody: JSON.stringify(message),
MessageGroupId: groupId,
};
if (!!dedupeId) {
sqsMessageRequest.MessageDeduplicationId = dedupeId;
}
return await new SQS().sendMessage(sqsMessageRequest).promise();
}
}
import AWS = require('aws-sdk');
const URL = 'URL';
const MESSAGE = 'MESSAGE';
const GROUP_ID = 'GROUP_ID';
const DEDUPE_ID = 'DEDUPE_ID';
const BAD_REQUEST = 'BAD_REQUEST';
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('EventService', () => {
beforeEach(() => {
(sqs.sendMessage().promise as jest.MockedFunction < any > ).mockReset();
});
afterAll(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
jest.resetAllMocks();
});
describe('sendFifoMessage', () => {
const messageResult = {
QueueUrl: URL,
MessageBody: JSON.stringify(MESSAGE),
MessageGroupId: GROUP_ID,
MessageDeduplicationId: DEDUPE_ID,
};
it('sendMessage successfully', async() => {
(sqs.sendMessage().promise as jest.MockedFunction < any > ).mockResolvedValueOnce('mocked data');
await EventService.sendFifoMessage(URL, MESSAGE, GROUP_ID, DEDUPE_ID);
expect.assertions(2);
expect(sqs.sendMessage).toBeCalledWith(messageResult);
expect(sqs.sendMessage().promise).toBeCalledTimes(1);
});
it('sendMessage throws', async() => {
(sqs.sendMessage().promise as jest.MockedFunction < any > ).mockRejectedValueOnce(BAD_REQUEST);
expect(async() => await EventService.sendFifoMessage(URL, MESSAGE, GROUP_ID, DEDUPE_ID)).rejects.toThrowError(
new Error(BAD_REQUEST),
);
expect(sqs.sendMessage).toBeCalledWith(messageResult);
expect(sqs.sendMessage().promise).toBeCalledTimes(1);
});
});
});
Upvotes: 0
Reputation: 222503
promise
is stubbed and returns undefined
, this is the reason why it doesn't return a promise that could be chained. It's supposed to return a promise, as the name suggests.
Since values may be different in different tests, it's better to expose it as a variable. sendMessage
can be exposed as well for assertions:
const mockPromiseFn = jest.fn();
const mockSendMessage = jest.fn().mockReturnThis();
jest.mock('aws-sdk', () => {
return {
SQS: jest.fn().mockReturnValue({
sendMessage: mockSendMessage,
promise: mockPromiseFn
})
};
});
It doesn't make sense to test it with await expect(sqs.sendMessage).rejects...
because it tests the code you've just written.
It likely should be:
mockPromiseFn.mockRejectedValue(new Error(...));
await expect(QueueClass.send(data)).rejects.toThrowError(...);
expect(mockSendMessage).toBeCalledWith(...);
This is potentially a mistake:
async send(data) {
try {
return SQS.sendMessage(...);
} catch (error) {
// Throw exception ...
}
}
try..catch
is unable to catch asynchronous errors from async
return, also sendMessage
return value wasn't converted to a promise.
It should be:
async send(data) {
try {
return await SQS.sendMessage(...).promise();
} catch (error) {
// Throw exception ...
}
}
Upvotes: 1