jest unit test for AWS lambda - javascript

I am new to Node.js. I was trying to write a jest unit test cases for AWS lambda function(for node environment). I used a node module called "lambda-tester" to test it. But the problem with "lambda-tester" is, it will hit the actual service and return the data. I don't want to do that. I need to mock the service call.
So, I wanted to go with the plain old way. But, I have issues with mocking it. Can you help me to write basic unit test case for the below lambda ith mocking the function "serviceFunction" ?
const dataService = require('../dataService');
exports.lambdaService = async event => {
let response = await serviceFunction(event.id);
if (response.code == 200) {
return response;
} else {
return {
statusCode: response.code,
body: JSON.stringify({
message: response.message
})
};
}
};
const serviceFunction = async id => {
return await dataService.retrieveData(id);
};

You can use jest.spyOn(object, methodName, accessType?) method to mock dataService.retrieveData method. And, your serviceFunction function only has one statement, so you can test lambdaService function with it together.
E.g.
index.js:
const dataService = require('./dataService');
exports.lambdaService = async event => {
let response = await serviceFunction(event.id);
if (response.code == 200) {
return response;
} else {
return {
statusCode: response.code,
body: JSON.stringify({
message: response.message
})
};
}
};
const serviceFunction = async id => {
return await dataService.retrieveData(id);
};
dataService.js:
module.exports = {
retrieveData: async id => {
return { code: 200, data: 'real data' };
}
};
index.spec.js:
const { lambdaService } = require('.');
const dataService = require('./dataService');
describe('lambdaService', () => {
beforeEach(() => {
jest.restoreAllMocks();
});
test('should return data', async () => {
const mResponse = { code: 200, data: 'mocked data' };
const mEvent = { id: 1 };
const retrieveDataSpy = jest.spyOn(dataService, 'retrieveData').mockResolvedValueOnce(mResponse);
const actualValue = await lambdaService(mEvent);
expect(actualValue).toEqual(mResponse);
expect(retrieveDataSpy).toBeCalledWith(mEvent.id);
});
test('should return error message', async () => {
const mResponse = { code: 500, message: 'Internal server error' };
const mEvent = { id: 1 };
const retrieveDataSpy = jest.spyOn(dataService, 'retrieveData').mockResolvedValueOnce(mResponse);
const actualValue = await lambdaService(mEvent);
expect(actualValue).toEqual({ statusCode: 500, body: JSON.stringify({ message: mResponse.message }) });
expect(retrieveDataSpy).toBeCalledWith(mEvent.id);
});
test('should throw an error', async () => {
const mEvent = { id: 1 };
const retrieveDataSpy = jest.spyOn(dataService, 'retrieveData').mockRejectedValueOnce(new Error('network error'));
await expect(lambdaService(mEvent)).rejects.toThrowError(new Error('network error'));
expect(retrieveDataSpy).toBeCalledWith(mEvent.id);
});
});
Unit test result with coverage report:
PASS src/stackoverflow/58623194/index.spec.js
lambdaService
✓ should return data (6ms)
✓ should return error message (4ms)
✓ should throw an error (2ms)
----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------------|----------|----------|----------|----------|-------------------|
All files | 90 | 100 | 66.67 | 90 | |
dataService.js | 50 | 100 | 0 | 50 | 3 |
index.js | 100 | 100 | 100 | 100 | |
----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 4.619s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58623194

Related

Mocking Segment Analytics with Jest in TS

I'm trying to write a unit test that checks if the .track method of the Analytics is called. For some reason the test keeps failing, although calling the function via http does trigger the call. I'm not sure if I've mocked it wrong, or what the issue could be?
index.ts:
import { Request } from "../types"
import { getSecret } from "../src/secrets"
import Analytics from "analytics-node"
const logger = (req: Request) => {
const analytics = new Analytics(<string>process.env.WRITE_KEY);
return analytics.track({
userId: req.userId
});
}
export default logger
index.test.ts:
jest.mock('analytics-node');
import { Request } from "../types"
import logger from "./index"
import Analytics from "analytics-node"
const mockAnalytics = new Analytics(process.env.WRITE_KEY = 'test');
describe('Logger tests', () => {
it(`Should call analytics.track`, () => {
const request: Request = {
userId: 23
}
return logger(request).then(() => {
expect(mockAnalytics.track).toHaveBeenCalled()
});
});
});
You are using Automatic mock by calling jest.mock('analytics-node').
Calling jest.mock('analytics-node') returns a useful "automatic mock" you can use to spy on calls to the class constructor and all of its methods. It replaces the ES6 class with a mock constructor and replaces all of its methods with mock functions that always return undefined. Method calls are saved in theAutomaticMock.mock.instances[index].methodName.mock.calls.
E.g.
index.ts:
import Analytics from 'analytics-node';
export interface Request {
userId: string | number;
}
const logger = (req: Request) => {
const analytics = new Analytics(<string>process.env.WRITE_KEY);
return analytics.track({
userId: req.userId,
anonymousId: 1,
event: '',
});
};
export default logger;
index.test.ts:
import logger, { Request } from './';
import Analytics from 'analytics-node';
jest.mock('analytics-node');
const mockAnalytics = Analytics as jest.MockedClass<typeof Analytics>;
describe('Logger tests', () => {
afterAll(() => {
jest.resetAllMocks();
});
it(`Should call analytics.track`, () => {
const WRITE_KEY = process.env.WRITE_KEY;
process.env.WRITE_KEY = 'test key';
const request: Request = {
userId: 23,
};
logger(request);
expect(mockAnalytics).toBeCalledWith('test key');
expect(mockAnalytics.mock.instances[0].track).toHaveBeenCalled();
process.env.WRITE_KEY = WRITE_KEY;
});
});
unit test result:
PASS examples/65412302/index.test.ts
Logger tests
✓ Should call analytics.track (4 ms)
----------|---------|----------|---------|---------|-------------------
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: 1 passed, 1 total
Snapshots: 0 total
Time: 5.954 s
By using "#segment/analytics-next": "1.49.2"
let analyticsInstance: AnalyticsBrowser | null | undefined;
export const loadAnalytics = (props?: LoadAnalyticsProps): typeof analyticsInstance => {
if ((analyticsInstance && analyticsInstance.instance?.initialized) || !props) {
return analyticsInstance;
}
const { segmentAnalyticsKey, anonymousId, userId } = props;
if (!segmentAnalyticsKey) {
return null;
}
analyticsInstance = AnalyticsBrowser.load({ writeKey: segmentAnalyticsKey });
analyticsInstance.setAnonymousId(anonymousId);
analyticsInstance.identify(userId);
return analyticsInstance;
};
mock
const setAnonymousIdMock = jest.fn();
const identifyMock = jest.fn();
jest.mock("#segment/analytics-next", () => {
const originalModule = jest.requireActual("#segment/analytics-next");
return {
__esModule: true,
...originalModule,
AnalyticsBrowser: {
load: () => {
return {
setAnonymousId: setAnonymousIdMock,
identify: identifyMock,
instance: {
initialized: true
}
};
}
}
};
});

How to stub a function that was called on api request in node js using sinon

//routes.js
app.get('/:id/info',
UnoController.getGameInfo,
...
);
//UnoController.js
async function getGameInfo(req, res) {
data = await UnoModel.getGameInfo(req.params.id);
if(data==null) return res.status(404).json({message:'Room Not Found'});
res.json(data);
}
//UnoModel.js
exports.getGameInfo = async function (id) {
return await mongodb.findById('uno', id);
}
I am writing unit testing in node js using sinon.
I want stub the UnoModel.getGameInfo to return {id:'123456789012'}, When i hit /someid/info rest api.
I wrote the test case like below.
//UnoApiTest.js
it('get game info with player payload and invalid room id', function (done) {
sinon.stub(UnoModel, 'getGameInfo').resolves({ id: '123456789012' });
request({
url: 'http://localhost:8080/api/v1/game/uno/123456789012/info',
headers: { 'x-player-token': jwt.sign({ _id: '123' }) }
}, function (error, response, body) {
expect(response.statusCode).to.equal(200);
done();
});
});
But i am receiving the statusCode as 404.
I tried to console the data. Its actually fetching from db.
It doesn't returns the provided value for stub.
Can anyone help me with this?
Is there any other way to do this?
It should work. E.g.
server.js:
const express = require('express');
const UnoController = require('./UnoController');
const app = express();
app.get('/api/v1/game/uno/:id/info', UnoController.getGameInfo);
module.exports = app;
UnoController.js:
const UnoModel = require('./UnoModel');
async function getGameInfo(req, res) {
const data = await UnoModel.getGameInfo(req.params.id);
if (data == null) return res.status(404).json({ message: 'Room Not Found' });
res.json(data);
}
exports.getGameInfo = getGameInfo;
UnoModel.js:
// simulate mongodb
const mongodb = {
findById(arg1, arg2) {
return { name: 'james' };
},
};
exports.getGameInfo = async function(id) {
return await mongodb.findById('uno', id);
};
UnoApiTest.test.js:
const app = require('./server');
const UnoModel = require('./UnoModel');
const request = require('request');
const sinon = require('sinon');
const { expect } = require('chai');
describe('61172026', () => {
const port = 8080;
let server;
before((done) => {
server = app.listen(port, () => {
console.log(`http server is listening on http://localhost:${port}`);
done();
});
});
after((done) => {
server.close(done);
});
it('should pass', (done) => {
sinon.stub(UnoModel, 'getGameInfo').resolves({ id: '123456789012' });
request({ url: 'http://localhost:8080/api/v1/game/uno/123456789012/info' }, function(error, response, body) {
expect(response.statusCode).to.equal(200);
done();
});
});
});
API automation test results with coverage report:
61172026
http server is listening on http://localhost:8080
✓ should pass
1 passing (50ms)
------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------------|---------|----------|---------|---------|-------------------
All files | 80 | 50 | 33.33 | 85.71 |
UnoController.js | 83.33 | 50 | 100 | 100 | 5
UnoModel.js | 50 | 100 | 0 | 50 | 4,8
server.js | 100 | 100 | 100 | 100 |
------------------|---------|----------|---------|---------|-------------------
source code: https://github.com/mrdulin/expressjs-research/tree/master/src/stackoverflow/61172026

How to verify error message through jest mock for throw error inside catch block

I need help for jest mocking throw Error(JSON.stringify(studentErrorRes));. I am not able to jest mock the catch block properly, I can do partial verification that error is thrown. I can do try block without any issue. Usually, when there is an error I do mock using .mockRejectedValue, it does not work in this case. Can someone please help me, how shall I mock this?
When I jest mock, I can successfully verify that error is getting thrown, but I am unable to verify what would be the exact error message? If I have more keys inside const studentErrorRes how shall I verify that in my mock that all my keys have proper values as expected? I hope I am not overthinking.
import { SNSEvent } from 'aws-lambda';
export const studentAPIGetHandler = async (event: SNSEvent): Promise<any> => {
try {
const studentID = event.studentInfo.studentID;
const studentPortal = StudentService.getStudentInfo(studentID);
} catch (error) {
const studentErrorRes = {
apiName: SuudentAPIName.Student_Message,
myMessage: 'Unable to get student API response',
};
logger.error(studentErrorRes.myMessage, error);
throw Error(JSON.stringify(studentErrorRes));
}
};
Part of test case for catch block
it("Catch block test for error", async () => {
try {
await studentAPIGetHandler(event);
} catch(e) {
expect(e).toThrowError;
// this verifies that error is thrown , but not exact error message
}
});
Here is the unit test solution:
index.ts:
import { StudentService } from './student.service';
type SNSEvent = any;
const SuudentAPIName = { Student_Message: 'Student_Message' };
const logger = console;
export const studentAPIGetHandler = async (event: SNSEvent): Promise<any> => {
try {
const studentID = event.studentInfo.studentID;
const studentPortal = StudentService.getStudentInfo(studentID);
} catch (error) {
const studentErrorRes = {
apiName: SuudentAPIName.Student_Message,
myMessage: 'Unable to get student API response',
};
logger.error(studentErrorRes.myMessage, error);
throw Error(JSON.stringify(studentErrorRes));
}
};
student.service.ts:
export class StudentService {
public static getStudentInfo(id) {
return {} as any;
}
}
index.test.ts:
import { studentAPIGetHandler } from './';
import { StudentService } from './student.service';
describe('59871106', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('should get student info', async () => {
jest.spyOn(StudentService, 'getStudentInfo').mockReturnValueOnce({});
const mEvent = { studentInfo: { studentID: 1 } };
await studentAPIGetHandler(mEvent);
expect(StudentService.getStudentInfo).toBeCalledWith(1);
});
it('should handle error', async () => {
const mError = new Error('some error');
jest.spyOn(StudentService, 'getStudentInfo').mockImplementationOnce(() => {
throw mError;
});
jest.spyOn(console, 'error');
const mEvent = { studentInfo: { studentID: 1 } };
await expect(studentAPIGetHandler(mEvent)).rejects.toThrowError(
JSON.stringify({ apiName: 'Student_Message', myMessage: 'Unable to get student API response' }),
);
expect(console.error).toBeCalledWith('Unable to get student API response', mError);
expect(StudentService.getStudentInfo).toBeCalledWith(1);
});
});
Unit test results with coverage report:
PASS src/stackoverflow/59871106/index.test.ts (12.591s)
59871106
✓ should get student info (10ms)
✓ should handle error (12ms)
console.error node_modules/jest-mock/build/index.js:860
Unable to get student API response Error: some error
at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59871106/index.test.ts:16:20
at Generator.next (<anonymous>)
at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59871106/index.test.ts:8:71
at new Promise (<anonymous>)
at Object.<anonymous>.__awaiter (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59871106/index.test.ts:4:12)
at Object.it (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59871106/index.test.ts:15:40)
at Object.asyncJestTest (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:102:37)
at resolve (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
at new Promise (<anonymous>)
at mapper (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
at promise.then (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
at process._tickCallback (internal/process/next_tick.js:68:7)
--------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------------|----------|----------|----------|----------|-------------------|
All files | 92.31 | 100 | 66.67 | 91.67 | |
index.ts | 100 | 100 | 100 | 100 | |
student.service.ts | 50 | 100 | 0 | 50 | 3 |
--------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 14.685s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59871106

jest.mock - how to check that function inside function has been called

This is a part of my app:
import validationSchema from "./../_validation/report";
const reportModel = require("../models/report");
ctrl.addReport = async (req, res) => {
const { body } = req;
try {
const validatedData = await validationSchema.validate(body);
const report = await reportModel(req.dbConnection).addReport(validatedData);
// something more
} catch (err) {
throw boom.badRequest(err);
}
};
module.exports = ctrl;
I would like to write some unit test but I want to test that addReport from reportModel has been called with the correct argument.
To mock reportModel I use jest.mock like this:
const reportModel = require("../models/report");
jest.mock('../models/report', () => {
return () => {
return {
addReport: jest.fn(() => {
return true
})
}
}
});
// do some tests and call function which call addReport from reportModel
expect(reportModel().addReport).toHaveBeenCalledTimes(1);
I've got 0. How can I check that addReport from reportModel has been called?
You didn't mock ./models/report module correctly. Here is the solution:
ctrl.js:
import validationSchema from './_validation/report';
import reportModel from './models/report';
const ctrl = {};
ctrl.addReport = async (req, res) => {
const { body } = req;
const validatedData = await validationSchema.validate(body);
const report = await reportModel(req.dbConnection).addReport(validatedData);
};
module.exports = ctrl;
./models/report.js:
function reportModel(connection) {
return {
async addReport(data) {
console.log('addReport');
},
};
}
export default reportModel;
./_validation/report.js:
const validationSchema = {
validate(body) {
return body;
},
};
export default validationSchema;
ctrl.test.js:
import reportModel from './models/report';
const ctrl = require('./ctrl');
jest.mock('./models/report', () => {
const mReportModel = {
addReport: jest.fn(() => true),
};
return jest.fn(() => mReportModel);
});
describe('59431651', () => {
it('should pass', async () => {
const mReq = { body: 'mocked data', dbConnection: {} };
const mRes = {};
await ctrl.addReport(mReq, mRes);
expect(reportModel).toBeCalledWith(mReq.dbConnection);
expect(reportModel(mReq.dbConnection).addReport).toHaveBeenCalledTimes(1);
});
});
Unit test result with coverage report:
PASS src/stackoverflow/59431651/ctrl.test.js
59431651
✓ should pass (7ms)
----------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
59431651 | 100 | 100 | 100 | 100 | |
ctrl.js | 100 | 100 | 100 | 100 | |
59431651/_validation | 100 | 100 | 100 | 100 | |
report.js | 100 | 100 | 100 | 100 | |
----------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.682s, estimated 11s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59431651

Catch errors in async functions and test them via jest

I'm trying to catch errors in my async functions and test those functions using jest.
import Db from './lib/db'
const db = new Db()
export async function example (id) {
await db.connect().catch(err => console.error(err))
const Data = db.connection.collection('data')
return Data.updateOne(
{ id },
{ $set: { anything: 'else' } }
).catch(err => console.error(err))
}
But the test returns me the error:
TypeError: db.connect(...).catch is not a function
So what am I doing wrong? The testing or the catching?
test('should call mongoDB updateOne() method', async () => {
// SETUP
let Data = db.connection.collection('data')
Data.updateOne = jest.fn()
// EXECUTE
expect.assertions(1)
await example({
id: 'id'
}).then((res) => {
// VERIFY
expect(Data.updateOne).toHaveBeenCalled()
})
})
Here is the solution:
Db.ts:
import console = require('console');
export class Db {
public connection = {
collection(model: string) {
return {
async updateOne(...args) {
console.log('update one');
}
};
}
};
public async connect() {
console.log('connect db');
}
}
example.ts:
import { Db } from './Db';
const db = new Db();
export async function example(id) {
await db.connect().catch(err => console.error(err));
const Data = db.connection.collection('data');
return Data.updateOne({ id }, { $set: { anything: 'else' } }).catch(err => console.error(err));
}
example.spec.ts:
import { example } from './example';
import { Db } from './Db';
jest.mock('./Db.ts', () => {
const collectionMocked = {
updateOne: jest.fn()
};
const connectionMocked = {
collection: jest.fn(() => collectionMocked)
};
const DbMocked = {
connect: jest.fn(),
connection: connectionMocked
};
return {
Db: jest.fn(() => DbMocked)
};
});
const db = new Db();
describe('example', () => {
test('should call mongoDB updateOne() method', async () => {
const Data = db.connection.collection('data');
(db.connect as jest.MockedFunction<any>).mockResolvedValueOnce({});
(Data.updateOne as jest.MockedFunction<any>).mockResolvedValueOnce('mocked value');
const actualValue = await example('1');
expect(actualValue).toBe('mocked value');
expect(Data.updateOne).toHaveBeenCalledWith({ id: '1' }, { $set: { anything: 'else' } });
});
});
Unit test result with coverage report:
PASS src/stackoverflow/57515192/example.spec.ts
example
✓ should call mongoDB updateOne() method (6ms)
------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files | 75 | 100 | 33.33 | 100 | |
example.ts | 75 | 100 | 33.33 | 100 | |
------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.336s, estimated 6s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57515192

Categories