Jest - Mock promisified SQS calls - unit-testing

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.'));

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 ...
}
}

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);
});
});
});

Related

Jest: Matcher error: expected value must be a function

I'm using NestJS with Jest and getting Matcher error: expected value must be a function error when run following unit test. I have set invalid email in mockBody. Did I missed anything here?
app.service.ts
#Injectable()
export class UserService {
constructor(private emailService: EmailService) {}
async registerUserInquiry(user: UserDto): Promise<{ email: string }> {
try {
await sendEmail(user);
} catch (error) {
throw new HttpException('Something went wrong!', HttpStatus.BAD_REQUEST);
}
return {
email: user.email,
};
}
}
app.service.spec.ts
describe("registerUser()", () => {
it("Should throw bad request error when passing invalid data", async () => {
const mockBody: UserDto = {
name: "John Doe",
message: "Example inquiry message",
email: "#example",
mobile: "+60121234567",
};
expect(async () => await service.registerUserInquiry(mockBody)).toThrow(
new HttpException("Something went wrong!", HttpStatus.BAD_REQUEST)
);
});
});
email.config.ts
export const sendEmail = async (user: User) => {
const transporter = nodemailer.createTransport({
... // service & auth
});
const options = {
... // email info
};
await transporter.sendMail(options, function (error, info) {
try {
console.info(error);
return info;
} catch (error) {
console.error(error);
throw error;
}
});
};
Error:
Instead of this
expect(async () => await service.registerUserInquiry(mockBody)).toThrow(
new HttpException("Something went wrong!", HttpStatus.BAD_REQUEST)
);
Try this one
await except(service.registerUserInquiry(mockBody)).rejects.toThrowError(...)
Your function is a promise which means it is not throwing an error but instead it rejects.

Jest & AWS.DynamoDB.DocumentClient mocking

I'm trying to mock a call to AWS.DynamoDB.DocumentClient. I tried several solutions I found online, but I cannot get it to work.
This is my best effort so far:
import * as AWS from 'aws-sdk';
import * as dynamoDbUtils from '../../src/utils/dynamo-db.utils';
jest.mock("aws-sdk");
describe('dynamo-db.utils', () => {
describe('updateEntity', () => {
it('Should return', async () => {
AWS.DynamoDB.DocumentClient.prototype.update.mockImplementation((_, cb) => {
cb(null, user);
});
await dynamoDbUtils.updateEntity('tableName', 'id', 2000);
});
});
});
I get error message
Property 'mockImplementation' does not exist on type '(params: UpdateItemInput, callback?: (err: AWSError, data: UpdateItemOutput) => void) => Request<UpdateItemOutput, AWSError>'.ts(2339)
My source file:
import AWS from 'aws-sdk';
let db: AWS.DynamoDB.DocumentClient;
export function init() {
db = new AWS.DynamoDB.DocumentClient({
region: ('region')
});
}
export async function updateEntity(tableName: string, id: string, totalNumberOfCharacters: number): Promise<AWS.DynamoDB.UpdateItemOutput> {
try {
const params = {
TableName: tableName,
Key: { 'id': id },
UpdateExpression: 'set totalNumberOfCharacters = :totalNumberOfCharacters',
ExpressionAttributeValues: {
':totalNumberOfCharacters': totalNumberOfCharacters
},
ReturnValues: 'UPDATED_NEW'
};
const updatedItem = await db.update(params).promise();
return updatedItem;
} catch (err) {
throw err;
}
}
Please advise how can I properly mock the response of AWS.DynamoDB.DocumentClient.update
Have some way to do the that thing (I think so).
This is one of them:
You use AWS.DynamoDB.DocumentClient, then we will mock AWS object to return an object with DocumentClient is mocked object.
jest.mock("aws-sdk", () => {
return {
DynamoDB: {
DocumentClient: jest.fn(),
},
};
});
Now, AWS.DynamoDB.DocumentClient is mocked obj. Usage of update function like update(params).promise() => Call with params, returns an "object" with promise is a function, promise() returns a Promise. Do step by step.
updateMocked = jest.fn();
updatePromiseMocked = jest.fn();
updateMocked.mockReturnValue({
promise: updatePromiseMocked,
});
mocked(AWS.DynamoDB.DocumentClient).mockImplementation(() => {
return { update: updateMocked } as unknown as AWS.DynamoDB.DocumentClient;
});
mocked import from ts-jest/utils, updateMocked to check the update will be call or not, updatePromiseMocked to control result of update function (success/ throw error).
Complete example:
import * as AWS from 'aws-sdk';
import * as dynamoDbUtils from './index';
import { mocked } from 'ts-jest/utils'
jest.mock("aws-sdk", () => {
return {
DynamoDB: {
DocumentClient: jest.fn(),
},
};
});
describe('dynamo-db.utils', () => {
describe('updateEntity', () => {
let updateMocked: jest.Mock;
let updatePromiseMocked: jest.Mock;
beforeEach(() => {
updateMocked = jest.fn();
updatePromiseMocked = jest.fn();
updateMocked.mockReturnValue({
promise: updatePromiseMocked,
});
mocked(AWS.DynamoDB.DocumentClient).mockImplementation(() => {
return { update: updateMocked } as unknown as AWS.DynamoDB.DocumentClient;
});
dynamoDbUtils.init();
});
it('Should request to Dynamodb with correct param and forward result from Dynamodb', async () => {
const totalNumberOfCharacters = 2000;
const id = 'id';
const tableName = 'tableName';
const updatedItem = {};
const params = {
TableName: tableName,
Key: { 'id': id },
UpdateExpression: 'set totalNumberOfCharacters = :totalNumberOfCharacters',
ExpressionAttributeValues: {
':totalNumberOfCharacters': totalNumberOfCharacters
},
ReturnValues: 'UPDATED_NEW'
};
updatePromiseMocked.mockResolvedValue(updatedItem);
const result = await dynamoDbUtils.updateEntity(tableName, id, totalNumberOfCharacters);
expect(result).toEqual(updatedItem);
expect(updateMocked).toHaveBeenCalledWith(params);
});
});
});

Testing AWS Lambda using Jest and Sinon causing Timeout error

I am attempting to execute the following Jest test to test an AWS Lambda locally:
const sinon = require('sinon');
const AWS = require('aws-sdk');
const { handler } = require('../queue_manager.js');
let result = {
// some result
};
let sinonSandbox;
beforeEach((done) => {
sinonSandbox = sinon.createSandbox();
done();
})
afterEach((done) => {
sinonSandbox.restore()
done();
})
it('queue-manager', async () => {
sinonSandbox.stub(AWS.DynamoDB.DocumentClient.prototype, 'get').returns({
promise: function () {
return Promise.resolve(result);
}
});
const lambdaResponse = { code: 200, data: 'some mocked data' };
var callback = function() { };
var context = {}
const event = {
somedata: "data"
};
const actualValue = await handler(event, context, callback);
expect(actualValue).toEqual(result);
});
I am attempting to test processing after a DynamoDB call, however, the test fails with a: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout
I tried giving it more time but the same result so it is not that it cannot return successfully in 5 seconds, it is not returning all.
Anyone familiar with Sinon that could possibly point out my issue?
Post edited, fist edition pushed at the bottom of the answer a
Second thought
here is an example on how to test an asynchronous function with Jest
test('the data is peanut butter', done => {
function callback(data) {
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
then I think the missing part in your code is the done callback on the unit test function. could you try this:
it('queue-manager', async (done) => {
sinonSandbox.stub(AWS.DynamoDB.DocumentClient.prototype, 'get').returns({
promise: function () {
return Promise.resolve(result);
}
});
const lambdaResponse = { code: 200, data: 'some mocked data' };
var callback = function() { };
var context = {}
const event = {
somedata: "data"
};
const actualValue = await handler(event, context, callback);
expect(actualValue).toEqual(result);
done();
});
Another option could be to specify the number of assertions (instead of the callback done) :
// before the await handler(...)
expect.assertions(1);
Hope this help.
First lead was the dynamo db ressource hanging:
Quite often the lambda is not returning the result and then runs into timeout because the lambda function is waiting for resources ie: dynamoDB connection for instance.
You can configure the runtime to send the response immediately by setting context.callbackWaitsForEmptyEventLoop to false.
First line of the handler must be:
context.callbackWaitsForEmptyEventLoop = false

How to test async function with axios?

I need to test async function using mocha.
Tried to test function that returns Promise from axios. Looked through many examples with axios-mock-adapter to solve my issue. BUT: axios sends REAL request, not mock as expected.
describe ('login sendRequest', () => {
let sandbox = null;
before(() => {
sandbox = sinon.createSandbox();
});
after(() => {
sandbox.restore();
});
it('should create and return REST promise', done => {
const mockAdapter = new MockAdapter(axios);
const data = { response: true };
mockAdapter.onAny('http://google.com').reply(200, data);
const requestParams = {
method: 'post',
url: 'http://google.com',
data: {},
adapter: adapter,
};
logic.sendRequest(requestParams).then(response => {
console.log(response);
done();
}).catch(err => {
console.log(err);
});
});
});
logic.js
export async function sendRequest(requsetParams) {
return await requestSender.request(requsetParams);
}
Expected to get 200 response and mock data that was set before. Why I don't get the response I need? May someone help?

Testing catch block via jest mock

I'm trying to test the 'catch' block of an async redux action via jest, but throwing a catch in the mock causes the test as a whole to fail.
My action is as follows:
export function loginUser(username, password) {
return async dispatch => {
dispatch({type: UPDATE_IN_PROGRESS});
try {
let response = await MyRequest.postAsync(
'/login', {username: username, password: password}
);
dispatch({
type: USER_AUTHENTICATED,
username: response.username,
token: response.token,
role: response.role,
id: response.id
});
} catch (error) {
dispatch({type: USER_SIGNED_OUT});
throw error;
} finally {
dispatch({type: UPDATE_COMPLETE});
}
};
}
The test is trying to mock up 'MyRequest.postAsync' to throw an error and thus trigger the catch block, but the test just bails with a 'Failed' message
it('calls expected actions when failed log in', async() => {
MyRequest.postAsync = jest.fn(() => {
throw 'error';
});
let expectedActions = [
{type: UPDATE_IN_PROGRESS},
{type: USER_SIGNED_OUT},
{type: UPDATE_COMPLETE}
];
await store.dispatch(userActions.loginUser('foo', 'bar'));
expect(store.getActions()).toEqual(expectedActions);
});
Is there a way to trigger the catch block to execute in my test via a jest mock function (or any other way for that matter)? Would be annoying to not be able to test a large chunk of code (as all my requests work in the same way).
Thanks in advance for help with this.
I don't know if it's still relevant, but you can do it in this way:
it('tests error with async/await', async () => {
expect.assertions(1);
try {
await store.dispatch(userActions.loginUser('foo', 'bar'));
} catch (e) {
expect(e).toEqual({
error: 'error',
});
}
});
Here is a documentation about error handling
I had the same issue. For me the below works. Wrapping up the await with a try/catch
it('calls expected actions when failed log in', async() => {
MyRequest.postAsync = jest.fn(() => {
throw 'error';
});
let expectedActions = [
{type: UPDATE_IN_PROGRESS},
{type: USER_SIGNED_OUT},
{type: UPDATE_COMPLETE}
];
try {
await store.dispatch(userActions.loginUser('foo', 'bar'));
} catch(e) {
expect(store.getActions()).toEqual(expectedActions);
}
});
I set the instance variable which we will access in our testing function to undefined so that it will go to catch block.
PS : This might not be possible all the times as we might not be having variables all time
class APIStore {
async fetchProductsAPI() {
try {
const products = networkManager.fetch('products')
this.productsStore.setProducts(prodcuts)
}
catch(e) {
this.apiStatus = API_FAILED
this.apiError = e
}
}
}
Test case
it('Check API Error ', async () => {
const toCheckErrorStore = new APIStore()
// Setting products store to undefined so that execution goes to catch block
toCheckErrorStore.productsStore = undefined
await toCheckErrorStore.fetchProductsAPI()
expect(toCheckErrorStore.apiStatus).toBe(API_FAILED)
expect(toCheckErrorStore.apiError).toEqual(errorObjectIWantToCompareWith)
}