I have a NodeJS lambda function that I run on AWS. I want to write a simple test for the .handler function.
CODE
Here is the index.js code:
// importing dependencies
var mySQLWriter = require('./mySQLWriterService');
exports.handler = function(event, context, callback) {
console.log('Printing out JSON.stringify(event): ');
console.log(JSON.stringify(event));
event.Records.forEach((record) => {
if (record.eventName === 'INSERT') {
console.log('We have an INSERT happening.');
mySQLWriter(record, callback);
}
});
};
I want to write a simple test that would pass if mySQLWriter is called.
Using Mocha and Chai and with help from dashmud below, I have attempted to do this, but it isn't working, here is my indexTests.js code:
const chai = require('chai');
const expect = chai.expect;
const spies = require('chai-spies');
chai.use(spies);
const appStart = require('../index');
const mySQLWriter = require('../mySQLWriterService');
describe('lambda function', () => {
it('should call the mySQLWriter() function', () => {
const spy = chai.spy.on(mySQLWriter, 'mySQLWriter');
let event = {
Records: [
{
eventName: 'INSERT',
dynamodb: {
NewImage: {
DeviceId: { S: 'Device 000' },
TimeStamp: { S: '2018-03-20T11:15:31.668Z' },
Accuracy: { S: '5' },
Latitude: { S: '53.639645' },
Longitude: { S: '-1.782491' },
Speed: { S: '1' },
}
}
}
]
};
const context = {};
appStart.handler(event, context, () => {
expect(spy).to.have.been.called();
done();
})
});
});
When I run the test, I get:
Removed chai and used sinon instead
//const chai = require('chai');
//const expect = chai.expect;
const sinon = require('sinon');
//chai.use(sinon);
const SQLWriter = require('./mysqlwriterservice.js');
const appStart = require('./sinonsqlwriter');
describe('lambda function', () => {
it('should call the mySQLWriter() function', () => {
const spy = sinon.spy(SQLWriter, 'mySQLWriter');
let event = {
Records: [
{
eventName: 'INSERT',
dynamodb: {
NewImage: {
DeviceId: { S: 'Device 000' },
TimeStamp: { S: '2018-03-20T11:15:31.668Z' },
Accuracy: { S: '5' },
Latitude: { S: '53.639645' },
Longitude: { S: '-1.782491' },
Speed: { S: '1' },
}
}
}
]
};
const context = {};
appStart.handler(event, context, () => {
console.log("Call count"+spy.callCount)
//expect(spy).to.have.been.called();
})
});
});
// sinonsqlwriter.js
// importing dependencies
const SQLWriter = require('./mysqlwriterservice.js');
exports.handler = function(event, context, callback) {
console.log('Printing out JSON.stringify(event): ');
console.log(JSON.stringify(event));
event.Records.forEach((record) => {
if (record.eventName === 'INSERT') {
console.log('We have an INSERT happening.');
SQLWriter.mySQLWriter(record, callback);
SQLWriter.mySQLWriter(record, callback);
}
});
callback();
};
// mysqlwriterservice.js
I used the code from your shared link. Below is the updated answer:
Looks like you're not calling the handler function, so try changing
appStart()
to
appStart.handler(someEvent)
Your appStart is just a module. You need to call the handler inside your appStart (index.js) and do your assertions inside the callback.
const chai = require('chai');
const expect = chai.expect;
const spies = require('chai-spies');
chai.use(spies);
const appStart = require('../index');
describe('lambda function', () => {
it('should call the mySQLWriter() function', done => {
const spy = chai.spy.on(mySQLWriter, 'mySQLWriter');
const event = {};
const context = {};
appStart.handler(event, context, () => {
expect(spy).to.have.been.called();
done();
})
});
});
Update based on comment and updated question:
Based on your screenshot, it seems like mySQLWriterService.js exports an object with a mySQLWriter function.
This will not work.
var mySQLWriter = require('./mySQLWriterService');
I think is should be like this:
const mySQLWriter = require('./mySQLWriterService').mysqlWriter;
(I'm not 100% sure as you did not include the code inside your mySQLWriterService.js.)
P.S. Don't use var. Ever.
Related
I try mocking sqs.receiveMessage function which imported from aws-sdk.
Here is my code(sqsHelper.js):
const AWS = require("aws-sdk");
export default class SqsHelper {
static SqsGetMessagesTest = () => {
const sqs = new AWS.SQS({
apiVersion: serviceConfig.sqs.api_version,
region: serviceConfig.sqs.region,
});
const queueURL =
"https://sqs.us-west-2.amazonaws.com/<1234>/<4567>";
const params = {
AttributeNames: ["SentTimestamp"],
MaxNumberOfMessages: 10,
MessageAttributeNames: ["All"],
QueueUrl: queueURL,
VisibilityTimeout: 20,
WaitTimeSeconds: 20,
};
return new Promise((resolve, reject) => {
sqs.receiveMessage(params, async (recErr, recData) => {
if (recErr) {
reject(recErr);
} else if (recData.Messages) {
console.info(`Message count: ${recData.Messages.length}`);
resolve(recData.Messages);
}
});
});
};
}
And here is the test file(sqsHelper.test.js):
import SqsHelper from "../../helpers/sqsHelper.js";
import { SQS } from "aws-sdk";
const dumyData = { Messages: [{ name: "123", lastName: "456" }] };
const sqs = new SQS();
describe("Test SQS helper", () => {
test("Recieve message", async () => {
jest.spyOn(sqs, 'receiveMessage').mockReturnValue(dumyData);
// check 1
const res1 = await sqs.receiveMessage();
console.log(`res: ${JSON.stringify(res1, null, 2)}`)
expect(res1).toEqual(dumyData);
// check 2
const res2 = await SqsHelper.SqsGetMessagesTest();
console.log(`res2: ${JSON.stringify(res2, null, 2)}`);
expect(res2).toBe(dumyData);
});
});
The problem is that on the first check( which i call the function directly from the test file) i can see that the receiveMessage has been mocked and the results is as expected.
But on the second check(which the function called from the second module "sqsHelper.js") looks that the mock function doe's work and the originalreceiveMessage has been called and it still ask me about credentials.
This is the error:
InvalidClientTokenId: The security token included in the request is
invalid.
what I'm doing wrong?
Thanks
The receiveMessage should trigger a callback that comes in the params. receiveMessage does not return a Promise
Try something like this:
const dummyData = { Messages: [{ name: "123", lastName: "456" }] };
const mockReceiveMessage = jest.fn().mockImplementation((params, callback) => callback("", dummyData));
jest.mock("aws-sdk", () => {
const originalModule = jest.requireActual("aws-sdk");
return {
...originalModule,
SQS: function() { // needs to be function as it will be used as constructor
return {
receiveMessage: mockReceiveMessage
}
}
};
})
describe("Test SQS helper", () => {
test("Recieve message", async () => {
const res = await SqsHelper.SqsGetMessagesTest();
expect(res).toBe(dummyData.Messages);
});
test("Error response", async () => {
mockReceiveMessage.mockImplementation((params, callback) => callback("some error"));
await expect(SqsHelper.SqsGetMessagesTest()).rejects.toEqual("some error");
});
});
Am writing unit test case for my code, as am calling another lambda function inside my lambda am not sure how to mock the inner lambda value, so because of this my test case is getting timed out. Attaching my code below
Test case file
"use strict";
const sinon = require("sinon");
const AWS = require("aws-sdk");
const expect = require("chai").expect;
const models = require("common-lib").models;
const { Organization } = models;
const DATA_CONSTANTS = require("./data/deleteOrganization");
const wrapper = require("../../admin/deleteOrganization");
const sandbox = sinon.createSandbox();
describe("Start Test updateOrganization", () => {
beforeEach(() => {
sandbox.stub(Organization, "update").resolves([1]);
});
afterEach(async () => {
sandbox.restore();
});
it("Test 03: Test to check success returned by handler", async () => {
const mLambda = {
invoke: sinon.stub().returnsThis(),
promise: sinon.stub(),
};
const response = await wrapper.handler(
DATA_CONSTANTS.API_REQUEST_OBJECT_FOR_200
);
console.log({ response });
expect(response.statusCode).to.be.equal(200);
const body = JSON.parse(response.body);
expect(body.message).to.be.equal("Updated successfully");
});
});
Code function
exports.handler = asyncHandler(async (event) => {
InitLambda("userService-deleteOrganization", event);
const { id } = event.pathParameters;
if (isEmpty(id)) {
return badRequest({
message: userMessages[1021],
});
}
try {
const orgrepo = getRepo(Organization);
const [rowsUpdated] = await orgrepo.update(
{ isDeleted: true },
{ org_id: id }
);
if (!rowsUpdated) {
return notFound({
message: userMessages[1022],
});
}
const lambda = new AWS.Lambda({
region: process.env.region,
});
await lambda
.invoke({
FunctionName:
"user-service-" + process.env.stage + "-deleteOrganizationDetail",
InvocationType: "Event",
Payload: JSON.stringify({
pathParameters: { id },
headers: event.headers,
}),
})
.promise();
return success({
message: userMessages[1023],
});
} catch (err) {
log.error(err);
return failure({
error: err,
message: err.message,
});
}
});
It seems that you are not properly stubbing the AWS.Lambda object.
try this,
const sinon = require("sinon");
const AWS = require("aws-sdk");
const expect = require("chai").expect;
const models = require("common-lib").models;
const { Organization } = models;
const DATA_CONSTANTS = require("./data/deleteOrganization");
const wrapper = require("../../admin/deleteOrganization");
const sandbox = sinon.createSandbox();
describe("Start Test updateOrganization", () => {
beforeEach(() => {
sandbox.stub(Organization, "update").resolves([1]);
});
afterEach(async () => {
sandbox.restore();
});
it("Test 03: Test to check success returned by handler", async () => {
const mLambda = { invoke: sinon.stub().returnsThis(), promise: sinon.stub() };
// you missed the below line
sinon.stub(AWS, 'Lambda').callsFake(() => mLambda);
const response = await wrapper.handler(
DATA_CONSTANTS.API_REQUEST_OBJECT_FOR_200
);
console.log({ response });
expect(response.statusCode).to.be.equal(200);
const body = JSON.parse(response.body);
expect(body.message).to.be.equal("Updated successfully");
sinon.assert.calledOnce(AWS.Lambda);
sinon.assert.calledWith(mLambda.invoke, {});
sinon.assert.calledOnce(mLambda.promise);
});
});
I can see that,
You are writing entire logic inside your handler function. This makes it less testable.
To overcome this you can write your code in such a way that is divided into small functions, which are easy to mock in test case files or testable independently. Handler function should only make call to those functions and return the result to the caller.
for Eg.
Lambda Handler:
exports.lambdaHandler = async (event) => {
// do some init work here
const lambdaInvokeResponse = await exports.invokeLambda(params);
}
exports.invokeLambda = async (params) {
const response = await lambda.invoke(params).promise();
return response;
}
test cases:
it('My Test Case - Success', async () => {
const result = await app.lambdaHandler(event);
const invikeLambdaResponse = {
// some dummy response
};
sinon.replace(app, 'invokeLambda', sinon.fake.returns(invikeLambdaResponse ));
});
This is now mocking the only lambda invoke part.
You can mock all the external calls like this (dynamodb, invoke, sns, etc.)
You can set spy and check if the called method is called as per desired arguments
I have a nested function that I'd like to use sinon.spy on (helper.postAlbum) to extract its return values. However, when console.log(spy.returnValues[0]) I get an undefined.
Here's a rough setup
album.js
const createAlbum = async (pictures) {
let promiseArray = await Promise.all(pic.map => {
return helper.postAlbum(pic, pic.catergory)
})
}
module.exports = {createAlbum}
helper.js
const postAlbum = async (picture, catergory) => {
const options = {
headers: {'X-Custom-Header': 'value'}
};
return axios.post('/save', { picture: picture, category:category }, options);
}
test.js
const sinon = require('sinon');
const helper = require('./helper');
describe('album create', ()=> {
let spy = sinon.spy(helper, 'postAlbum');
chai.request(app)
.post('/create')
.end(async (err, res) => {
expect(spy).to.have.been.calledOnce;
expect(spy.returnValues[0]).to.have.property('date').to.not.be.null;
})
})
I have developed the node js code as MVC architecture. The folder structure is Controller --> service -> model. And I have tried to write unit testing for the following code. Unfortunately, I couldn't mock the service function. So please help me to resolve it.
Controller
const SubscriberService = require('../../services/subscriber/subscriberService')
const response = require("../../config/response");
const constant = require('../../config/constant');
const SubscriberAnswerService = require('../../services/subscriberAnswer/subscriberAnswerService');
const path = require('path');
class SubscriberController {
constructor() {
this.subscriberService = new SubscriberService();
this.subscriberAnswerService = new SubscriberAnswerService();
}
async getSubscriber(req, res) {
try {
var { userId } = req;
const user = await this.subscriberService.findByUserId(userId);
if (user != null) {
res.send(response.res(true, constant.MSG.USER_DETAILS, user))
} else {
res.status(404).send(response.res(false, constant.MSG.USER_NOT_FOUND));
}
} catch (error) {
res.status(constant.RESPONSE.INTERNAL_ERROR.CODE)
.send(response.res(false, error.message))
}
}
}
Service
async findByUserId(id) {
const user = await Subscriber.findOne({ where: { id: id, status: 1 } });
return user;
}
Unit Testing Code
describe("Test SubscriberController", () => {
it("Test getsubscriber", async () => {
req.userId = 1;
jest.spyOn(subscriberService, "findByUserId").mockReturnValue(subscriberResponse);
await subscriberController.getSubscriber(req, res);
expect(res.statusCode).toBe(500);
});
});
Issue: I have mocked the service function which findByUserId but it does not work. It is given the following error.
error TypeError: Cannot read property 'findOne' of undefined
Please give the solution to mock findByUserId function.
Subscriber.Controller.test.js
const subscriberModel = require("../src/models/subscriber/subscriberModel");
const SubscriberService = require("../src/services/subscriber/subscriberService");
const SubscriberController = require("../src/controllers/Subscriber/subscriberController");
const subscriberController = new SubscriberController();
const subscriberService = new SubscriberService();
const httpMocks = require("node-mocks-http");
jest.mock("../src/models/subscriber/subscriberModel");
beforeEach(() => {
jest.resetAllMocks();
req = httpMocks.createRequest();
res = httpMocks.createResponse();
next = jest.fn();
jest.resetAllMocks();
subscriberModel.findOne = jest.fn();
});
// subscriberService.findByUserId = jest.fn();
const subscriberResponse = {
id: 1,
name: 'Sandun',
msisdn: '94704377575',
otp: '1234',
deleted: 0,
attempts: 0,
img_url: 'https://'
}
jest.mock('../src/models/subscriber/subscriberModel', () => () => {
const SequelizeMock = require("sequelize-mock");
let dbMock = new SequelizeMock();
let subscriberMock = dbMock.define('subscribers', {
id: 1,
name: 'Sandun',
msisdn: '94704377575',
otp: '1234',
deleted: 0,
attempts: 0,
img_url: 'https://'
});
let groupMock = dbMock.define('winner', {});
subscriberMock.belongsTo(groupMock);
subscriberMock.hasMany();
});
// This test shows how the constructor can be mocked, and how to spy on passed parameters.
describe("Test SubscriberController", () => {
it("Test getsubscriber", async () => {
req.userId = 1;
jest.spyOn(subscriberService, "findByUserId").mockReturnValue(subscriberResponse);
await subscriberController.getSubscriber(req, res);
expect(res.statusCode).toBe(200);
});
});
I am trying to test an AWS lambda function (node.js) created using AWS SAM. My function uses the npm module serverless-mysql to connect to Aurora. The following are the relevant parts of my lambda function:
const connection = require('serverless-mysql')({
config: {
host : process.env.DB_HOST,
user : process.env.DB_USER,
password : process.env.DB_PASSWORD
}
});
exports.lambdaHandler = async (event, context) => {
try {
const name = event.pathParameters.name;
const rows = await connection.query('SELECT * FROM users WHERE name = ?', [name]);
await connection.end()
const user = rows[0];
return {
'statusCode': 200,
'body': JSON.stringify({
firstName: user.first_name,
lastName: user.last_name,
bk: user.bk,
team: user.current_team
})
}
} catch (err) {
console.log(err);
return err;
}
};
I am trying to tests this by mocking the serverless-mysql dependency, but I am currently unable to do so. My test looks like this:
const app = require('../../app.js');
const chai = require('chai');
const sinon = require('sinon');
const expect = chai.expect;
const event = {
pathParameters: {
name: 'johndoe'
}
}
var context;
var successConnectionObject = {
connect: function() {
return Promise.resolve();
},
query: function(sqlQuery, params) {
return Promise.resolve('');
},
end: function() {}
}
var mysql = require('serverless-mysql');
var stub = sinon.stub(mysql, 'connect').returns(successConnectionObject);
describe('Tests', function () {
it('verifies successful response', async () => {
const result = await app.lambdaHandler(event, context);
expect(result).to.be.an('object');
expect(result.statusCode).to.equal(200);
mock.verify();
mock.restore();
});
});
However, this returns the following error:
TypeError: Cannot stub non-existent own property connect
I believe this is because mysql is not instantiated. So I replaced the line var mysql = require('serverless-mysql'); with:
var mysql = require('serverless-mysql')();
Unfortunately, this results in the following error:
AssertionError: expected [Error: Error: self signed certificate in certificate chain] to be an object
So it seems the connect() method of the real serverless-mysql module is called.
How can I correctly mock serverless-mysql using sinon?
require('serverless-mysql') returns a function that returns a different value each time it is called, so mocking the properties on the result of one call won't affect the returned value of a different call.
That means you have to mock the function itself, which means mocking the entire module.
sinon doesn't provide a way to mock an entire module so you'll have to use something else for that part.
Here is a working test using proxyquire to mock the serverless-mysql module:
const chai = require('chai');
const sinon = require('sinon');
const expect = chai.expect;
const proxyquire = require('proxyquire');
const event = {
pathParameters: {
name: 'johndoe'
}
}
var context;
var successConnectionObject = {
connect: function () {
return Promise.resolve();
},
query: function (sqlQuery, params) {
return Promise.resolve([{
first_name: 'first',
last_name: 'last',
bk: 'bk',
current_team: 'team'
}]);
},
end: function () { }
}
const stub = sinon.stub().returns(successConnectionObject);
const app = proxyquire('../../app.js', { 'serverless-mysql': stub });
describe('Tests', function () {
it('verifies successful response', async () => {
const result = await app.lambdaHandler(event, context);
expect(result).to.be.an('object'); // Success!
expect(result.statusCode).to.equal(200); // Success!
sinon.assert.calledWithExactly(stub, {
config: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD
}
}); // Success!
});
});