Writing unit test for the azure redis cache in nodejs - node.js

I am trying to write a unit test case using mocha framework, where I have to mock an azure redis cache...Can someone help me on how to mock cache for unit test case purpose using node.js.
const redis = require('redis');
const redisHostName = process.env['hostName'];
const redisPort = process.env['port'];
const redisKey = process.env['key'];
let client = redis.createClient(redisPort, redisHostName, { auth_pass: redisKey, tls: { serverName: redisHostName } });
async function getKeyValue(key, ctx) {
context = ctx;
return new Promise((resolve, reject) => {
client.get(key, function (err, result) {
if (err) {
resolve(err);
}
resolve(result);
});
});
}
getKeyValue();

Here is the unit test solution:
index.js
const redis = require('redis');
const redisHostName = process.env['hostName'];
const redisPort = process.env['port'];
const redisKey = process.env['key'];
let client = redis.createClient(redisPort, redisHostName, { auth_pass: redisKey, tls: { serverName: redisHostName } });
async function getKeyValue(key, ctx) {
context = ctx;
return new Promise((resolve, reject) => {
client.get(key, function(err, result) {
if (err) {
return resolve(err);
}
resolve(result);
});
});
}
exports.getKeyValue = getKeyValue;
index.test.js:
const sinon = require('sinon');
const redis = require('redis');
describe('60870408', () => {
beforeEach(() => {
process.env['hostName'] = '127.0.0.1';
process.env['port'] = 6379;
process.env['key'] = 'test';
});
afterEach(() => {
delete require.cache[require.resolve('./')];
sinon.restore();
});
it('should get value', async () => {
const client = {
get: sinon.stub().callsFake((key, callback) => callback(null, 'elsa')),
};
const createClientStub = sinon.stub(redis, 'createClient').returns(client);
const { getKeyValue } = require('./');
sinon.assert.calledWithExactly(createClientStub, '6379', '127.0.0.1', {
auth_pass: 'test',
tls: { serverName: '127.0.0.1' },
});
const actual = await getKeyValue('name', {});
sinon.assert.match(actual, 'elsa');
sinon.assert.calledWithExactly(client.get, 'name', sinon.match.func);
});
it('should handle error', async () => {
const mError = new Error('network');
const client = {
get: sinon.stub().callsFake((key, callback) => callback(mError)),
};
const createClientStub = sinon.stub(redis, 'createClient').returns(client);
const { getKeyValue } = require('./');
sinon.assert.calledWithExactly(createClientStub, '6379', '127.0.0.1', {
auth_pass: 'test',
tls: { serverName: '127.0.0.1' },
});
const actual = await getKeyValue('name', {});
sinon.assert.match(actual, mError);
sinon.assert.calledWithExactly(client.get, 'name', sinon.match.func);
});
});
unit test results with 100% coverage:
60870408
✓ should get value
✓ should handle error
2 passing (28ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------

Related

TypeError: AWS.SecretsManager is not a constructor in unit testing with proxyquire

I have written a test code to test code that gives credentials from AWS Secret Manager. I used proxyquire and sinon for stubbing and getting this error.
Function I want to test
exports.getCredsFromAWSSecretsManager = (keyName) => {
const SM = new AWS.SecretsManager({
apiVersion: process.env.AWS_SM_API_VERSION,
region: process.env.AWS_SM_REGION
});
return SM.getSecretValue(params).promise().then((data) => {
logger.info(logMsgs.awsHlpr_smGetSecretValueSuccess(JSON.stringify(data)));
return JSON.parse(data.SecretString);
}).catch((err) => {
logger.error(logMsgs.awsHlpr_smGetSecretValueErr(JSON.stringify(err)));
throw err;
});
};
Test case that I have written
const sinon = require("sinon");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
const { mockLogger } = require("../../mockdata/mockLogger");
let awsHelper;
let secretsManagerStub;
describe.only("AWS Helper ", () => {
// function1
describe("AWS Helper: getCredsFromAWSSecretsManagera method", () => {
before((done) => {
const data = {
SecretString: JSON.stringify({ publicKey: 'secretUsername', privateKey: 'secretPassword' }),
};
secretsManagerStub = {
getSecretValue: sinon.stub().callsFake((params, callback) => {
callback(null, data);
}),
};
const awsStub = {
SecretsManager: sinon.stub().returns(secretsManagerStub)
}
awsHelper = proxyquire('../../../utils/aws_helper.js', {
'aws-sdk':{
AWS:awsStub
} ,
"../../utils/logger": mockLogger,
});
done();
});
afterEach(() => {
sinon.restore();
});
it('should write random data!', async () => {
const expectedData = "abcdef";
secretsManagerStub.getSecretValue.yields(null, expectedData);
const data = await awsHelper.getCredsFromAWSSecretsManager();
sinon.assert.callCount(secretsManagerStub.getSecretValue, 1);
assert.strictEqual(data, expectedData);
});
});
});
This code gives me the error saying
TypeError: AWS.SecretsManager is not a constructor
any help would be greatly appreciated.
AWS is a namespace, it contains all AWS service classes like SecretsManager. You should provide the awsStub to aws-sdk, there is no need to wrap the awsStub inside an object.
aws_helper.js:
const AWS = require('aws-sdk');
exports.getCredsFromAWSSecretsManager = () => {
const SM = new AWS.SecretsManager({
apiVersion: process.env.AWS_SM_API_VERSION,
region: process.env.AWS_SM_REGION,
});
const params = {
SecretId: '1',
};
return SM.getSecretValue(params)
.promise()
.then((data) => {
console.info(data);
return JSON.parse(data.SecretString);
})
.catch((err) => {
console.error(err);
throw err;
});
};
aws_helper.test.js:
const sinon = require('sinon');
const proxyquire = require('proxyquire').noCallThru().noPreserveCache();
let awsHelper;
let secretsManagerStub;
describe('AWS Helper: getCredsFromAWSSecretsManagera method', () => {
before(() => {
const data = {
SecretString: JSON.stringify({ publicKey: 'secretUsername', privateKey: 'secretPassword' }),
};
secretsManagerStub = {
getSecretValue: sinon.stub().returnsThis(),
promise: sinon.stub().resolves(data),
};
const awsStub = {
SecretsManager: sinon.stub().returns(secretsManagerStub),
};
awsHelper = proxyquire('./aws_helper.js', {
'aws-sdk': awsStub,
});
});
afterEach(() => {
sinon.restore();
});
it('should write random data!', async () => {
const data = await awsHelper.getCredsFromAWSSecretsManager();
sinon.assert.callCount(secretsManagerStub.getSecretValue, 1);
sinon.assert.match(data, { publicKey: 'secretUsername', privateKey: 'secretPassword' });
});
});
test result:
AWS Helper: getCredsFromAWSSecretsManagera method
{
SecretString: '{"publicKey":"secretUsername","privateKey":"secretPassword"}'
}
✓ should write random data!
1 passing (2s)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 77.78 | 100 | 66.67 | 77.78 |
aws_helper.js | 77.78 | 100 | 66.67 | 77.78 | 19-20
---------------|---------|----------|---------|---------|-------------------

fake pool connection using sinon js

I just want to fake pool connection and
use the connection in all my unit test.
const logger = require('./logger.js');
const { Pool } = require ('pg');
const proxyquire = require('proxyquire');
const sinon = require('sinon');
var assert = sinon.assert;
const pool = new Pool ({
connectionString: process.env.HEROKU_POSTGRESQL_BLUE_URL,
ssl: {
rejectUnauthorized: false
},
//max: 500
});
async function queryWithParameter(queryToExecute,parameterReq) {
var result;
var finalResult;
try{
const client = await pool.connect();
try{
if(parameterReq == null)
result = await client.query(queryToExecute);
else
result = await client.query(queryToExecute, parameterReq);
finalResult = result.rows;
}
catch(err){
logger.error('error in queryWithParameter : ' + err);
}
finally{
client.release(true);
}
}
catch (err){
}
return finalResult;
}
module.exports = {
queryWithParameter
};
I'm supposed to use sinon.js to fake the pool connection so I cannot hit the actual DB but failed to implement it successfully.
I will show you how to test the test cases that "should be the correct query result".
index.js:
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.HEROKU_POSTGRESQL_BLUE_URL,
ssl: {
rejectUnauthorized: false,
},
});
async function queryWithParameter(queryToExecute, parameterReq) {
var result;
var finalResult;
try {
const client = await pool.connect();
try {
if (parameterReq == null) result = await client.query(queryToExecute);
else result = await client.query(queryToExecute, parameterReq);
finalResult = result.rows;
} catch (err) {
console.error('error in queryWithParameter : ' + err);
} finally {
client.release(true);
}
} catch (err) {}
return finalResult;
}
module.exports = { queryWithParameter };
index.test.js:
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('69222273', () => {
it('should query result', async () => {
process.env.HEROKU_POSTGRESQL_BLUE_URL = 'whatever';
const res = { rows: [{ message: 'Hello world!' }] };
const clientStub = { query: sinon.stub().resolves(res), release: sinon.stub() };
const poolStub = { connect: sinon.stub().resolves(clientStub) };
const pgStub = { Pool: sinon.stub().returns(poolStub) };
const { queryWithParameter } = proxyquire('./', {
pg: pgStub,
});
const actual = await queryWithParameter('SELECT $1::text as message', ['Hello world!']);
sinon.assert.calledWithExactly(pgStub.Pool, {
connectionString: 'whatever',
ssl: {
rejectUnauthorized: false,
},
});
sinon.assert.calledOnce(poolStub.connect);
sinon.assert.calledWithExactly(clientStub.query, 'SELECT $1::text as message', ['Hello world!']);
sinon.assert.match(actual, [{ message: 'Hello world!' }]);
sinon.assert.calledWithExactly(clientStub.release, true);
});
});
test result:
69222273
✓ should query result (1350ms)
1 passing (1s)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 84.62 | 50 | 100 | 91.67 |
index.js | 84.62 | 50 | 100 | 91.67 | 22
----------|---------|----------|---------|---------|-------------------

In my unit test sandbox. why is it telling me the function I'm specifying is a non-existent property?

The following line in my unit test:
sandbox.stub(myAPI,'getPeople').returns([500, {errorCode: '500'}])
is giving me the following error:
TypeError: Cannot stub non-existent property getPeople
I'm trying to stub an error response from my API call to test my error handling.
The unit test:
const chai = require('chai');
const sinonChai = require('sinon-chai');
const sinon = require('sinon');
const { getPeopleHandler } = require('../services/handlers/get-people-handler');
const { expect } = chai;
const myAPI = require('../services/api');
chai.use(sinonChai);
describe('handle error', () => {
let req;
let res;
let sandbox;
describe('getPeopleHandler() with error', () => {
before(() => {
req = {
session: {}
};
res = {
render: () => ({})
};
sandbox = sinon.createSandbox();
});
beforeEach(() => {
sandbox.stub(myAPI,'getPeople').returns([500, {errorCode: '500'}]);
})
afterEach(() => {
sandbox.restore();
});
it('should render the error page', async () => {
sandbox.stub(res, 'render').returns({});
res.locals = {};
await getPeopleHandler(req, res);
expect(res.render).to.have.been.calledOnceWith('error.html');
});
});
});
api.js
const fetch = require('node-fetch');
const getPeople = (url) => {
console.log(`About to call API at ${url}`);
return new Promise((resolve, reject) => {
fetch(url, { method: 'GET' })
.then(res => Promise.all([res.status, res.json()]))
.then(([status, jsonData]) => {
resolve([status, jsonData]);
}).catch(error => {
console.log('e', error)
reject(error)
})
})
};
module.exports = getPeople;
get-people-handler.js
const callAPI = require('../../services/api');
const config = require('../../config/config');
const getPeopleHandler = async (req, res) => {
const url = config.getPeopledataUrl;
const response = await callAPI(url);
console.log(`API call status = ${response[0]}`);
if (response[0] === 200) {
res.locals.list = response[1];
res.render('people.html');
} else {
res.locals.error = response[0];
res.render('error.html');
}
};
module.exports = { getPeopleHandler };
You are stub the getPeople function because you use module.exports. If you want to export a object, you should do it like this:
module.exports = { getPeople };
sinon.stub(object, "method")
Replaces object.method with a stub function. An exception is thrown if the property is not already a function.
Below is a working example:
api.js:
const fetch = require('node-fetch');
const getPeople = (url) => {
console.log(`About to call API at ${url}`);
return new Promise((resolve, reject) => {
fetch(url, { method: 'GET' })
.then((res) => Promise.all([res.status, res.json()]))
.then(([status, jsonData]) => {
resolve([status, jsonData]);
})
.catch((error) => {
console.log('e', error);
reject(error);
});
});
};
module.exports = { getPeople };
config.js:
module.exports = {
getPeopledataUrl: 'http://localhost:3000/api/people',
};
get-people-handler.js:
const api = require('./api');
const config = require('./config');
const getPeopleHandler = async (req, res) => {
const url = config.getPeopledataUrl;
const response = await api.getPeople(url);
console.log(`API call status = ${response[0]}`);
if (response[0] === 200) {
res.locals.list = response[1];
res.render('people.html');
} else {
res.locals.error = response[0];
res.render('error.html');
}
};
module.exports = { getPeopleHandler };
get-people-handler.test.js:
const sinon = require('sinon');
const { getPeopleHandler } = require('./get-people-handler');
const myAPI = require('./api');
describe('handle error', () => {
let req;
let res;
let sandbox;
describe('getPeopleHandler() with error', () => {
before(() => {
req = {
session: {},
};
res = {
render: () => ({}),
};
sandbox = sinon.createSandbox();
});
beforeEach(() => {
sandbox.stub(myAPI, 'getPeople').returns([500, { errorCode: '500' }]);
});
afterEach(() => {
sandbox.restore();
});
it('should render the error page', async () => {
sandbox.stub(res, 'render').returns({});
res.locals = {};
await getPeopleHandler(req, res);
sinon.assert.calledWithExactly(res.render, 'error.html');
});
});
});
test result:
handle error
getPeopleHandler() with error
API call status = 500
✓ should render the error page
1 passing (7ms)
-----------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------------|---------|----------|---------|---------|-------------------
All files | 60.87 | 50 | 16.67 | 60.87 |
api.js | 30 | 100 | 0 | 30 | 4-13
config.js | 100 | 100 | 100 | 100 |
get-people-handler.js | 83.33 | 50 | 100 | 83.33 | 9-10
-----------------------|---------|----------|---------|---------|-------------------

How to mockup a constant defined in another file using sinon and rewire?

I'm a beginner with JS tests and I'm having issues when I try to mockup the value of a constant in the file I need to test.
I have the following file
// index.js
const { MultiAccounts } = require('../../some.js')
const MultiAccountInstance = new MultiAccounts();
...
const syncEvents = () => Promise.try(() => {
// ...
return MultiAccountInstance.all()
.then((accounts) => // ...); // ==> it throws the exception here Cannot read property 'then' of undefined
});
module.exports = syncEvents;
So, I will like to mockup the MultiAccountInstance constant.
I had been trying using Simon and rewire, but with the following script I'm having it throws the exception here Cannot read property 'then' of undefined exception in the script above.
//index.test.js
const rewire = require('rewire');
const indexRewired = rewire('.../../index/js');
describe('testing sync events', () => {
let fakeMultiAccountInstance, MultiAccountInstanceReverter;
let accounts;
beforeEach(() => {
accounts = [{id: 1}, {id: 2}];
fakeMultiAccountInstance = {};
fakeMultiAccountInstance.all = () => Promise.resolve(accounts);
MultiAccountInstanceReverter = indexRewired.__set__('MultiAccountInstance', fakeMultiAccountInstance);
});
afterEach(() => {
MultiAccountInstanceReverter();
});
it('testing', ()=> {
const spy = sinon.stub(fakeMultiAccountInstance, 'all');
return indexRewired().then((resp) => {
spy.restore();
expect(spy).to.have.been.calledWith({someParams: true});
});
})
});
How can I achieve this?. I also tried using stubs, but I'm having the error that the MultiAccountInstance.all is not a function
it's something like this
//index.test.js
const rewire = require('rewire');
const indexRewired = rewire('.../../index/js');
describe('testing sync events', () => {
let stubMultiAccountInstance, MultiAccountInstanceReverter;
let accounts;
beforeEach(() => {
accounts = [{id: 1}, {id: 2}];
stubMultiAccountInstance= sinon.stub().returns({
all: () => Promise.resolve(accounts), // also tried with sinon.stub().resolves(accounts)
});
MultiAccountInstanceReverter = indexRewired.__set__('MultiAccountInstance', stubMultiAccountInstance);
});
afterEach(() => {
stubMultiAccountInstance.reset();
MultiAccountInstanceReverter();
});
it('testing', ()=> {
return indexRewired().then((resp) => {
expect(stubMultiAccountInstance).to.have.been.calledWith({someParams: true});
});
})
});
Do you know what am I doing wrong?
Here is the unit test solution:
index.js:
const { MultiAccounts } = require('./some.js');
const Promise = require('bluebird');
let MultiAccountInstance = new MultiAccounts();
const syncEvents = () =>
Promise.try(() => {
return MultiAccountInstance.all().then((accounts) => console.log(accounts));
});
module.exports = syncEvents;
some.js:
function MultiAccounts() {
async function all() {}
return {
all,
};
}
module.exports = { MultiAccounts };
index.test.js:
const sinon = require('sinon');
const rewire = require('rewire');
const Promise = require('bluebird');
describe('61659908', () => {
afterEach(() => {
sinon.restore();
});
it('should pass', async () => {
const promiseTrySpy = sinon.spy(Promise, 'try');
const logSpy = sinon.spy(console, 'log');
const indexRewired = rewire('./');
const accounts = [{ id: 1 }, { id: 2 }];
const fakeMultiAccountInstance = {
all: sinon.stub().resolves(accounts),
};
indexRewired.__set__('MultiAccountInstance', fakeMultiAccountInstance);
await indexRewired();
sinon.assert.calledOnce(fakeMultiAccountInstance.all);
sinon.assert.calledWith(logSpy, [{ id: 1 }, { id: 2 }]);
sinon.assert.calledOnce(promiseTrySpy);
});
});
unit test results with coverage report:
61659908
[ { id: 1 }, { id: 2 } ]
✓ should pass (54ms)
1 passing (62ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 80 | 100 |
index.js | 100 | 100 | 100 | 100 |
some.js | 100 | 100 | 50 | 100 |
----------|---------|----------|---------|---------|-------------------

Sinon Calling a function passed as an argument to a constructor

I have the following classes A and B,
class A {
listenResponse(port, callback) {
const server = new CustomServer(port, (error, response) => {
if (error) return callback(error);
callback(null, response);
});
}
}
class CustomServer {
constructor(port, callback) {
this.port = port;
this.server = http.createServer((request, response) => {
if(// condition) { return callback(error); }
callback(null, response);
});
}
}
How do I test the function passed to CustomServer constructor while unit testing class A? Any help is highly appreciated.
Here is the unit test solution using an additional module proxyquire:
a.js:
const CustomServer = require('./customServer');
class A {
listenResponse(port, callback) {
const server = new CustomServer(port, (error, response) => {
if (error) return callback(error);
callback(null, response);
});
}
}
module.exports = A;
customServer.js:
class CustomServer {
constructor(port, callback) {
this.port = port;
this.server = http.createServer((request, response) => {
callback(null, response);
});
}
}
module.exports = CustomServer;
a.test.js:
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('60084056', () => {
it('should get response', () => {
const error = null;
const response = {};
const customServerStub = sinon.stub().yields(error, response);
const A = proxyquire('./a.js', {
'./customServer': customServerStub,
});
const a = new A();
const port = 3000;
const callback = sinon.stub();
a.listenResponse(port, callback);
sinon.assert.calledWithExactly(customServerStub, port, sinon.match.func);
sinon.assert.calledWithExactly(callback, null, response);
});
it('should handle error', () => {
const error = new Error('network');
const response = {};
const customServerStub = sinon.stub().yields(error, response);
const A = proxyquire('./a.js', {
'./customServer': customServerStub,
});
const a = new A();
const port = 3000;
const callback = sinon.stub();
a.listenResponse(port, callback);
sinon.assert.calledWithExactly(customServerStub, port, sinon.match.func);
sinon.assert.calledWithExactly(callback, error);
});
});
Unit test results with coverage report:
60084056
✓ should get response (1684ms)
✓ should handle error
2 passing (2s)
-----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files | 70 | 100 | 50 | 66.67 |
a.js | 100 | 100 | 100 | 100 |
customServer.js | 25 | 100 | 0 | 25 | 3-5
-----------------|---------|----------|---------|---------|-------------------
Source code: https://github.com/mrdulin/expressjs-research/tree/master/src/stackoverflow/60084056

Resources