can't mock constructor using sinon and proxyquire - node.js

I've looked at several similar questions but none of the cases fit my problem. I'm trying to mock a constructor, which I've done in other tests, but I can't get it to work in the case of using google-auth-library
code.js
const {OAuth2Client} = require('google-auth-library');
const keys = require('./oauth2.keys.json');
async function getRedirectUrl() {
const oAuth2Client = new OAuth2Client(
keys.installed.client_id,
keys.installed.client_secret,
keys.installed.redirect_uris[0]
);
const authorizeUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: 'https://www.googleapis.com/auth/bigquery',
prompt: 'consent'
});
return authorizeUrl;
}
test.js
let Code = require('../code.js');
describe('code', function() {
let generateUrlStub, tokenStub, mockClient;
before(async () => {
generateUrlStub = sinon.stub().returns('http://example.com');
tokenStub = sinon.stub().returns({tokens: 'tokens'});
mockClient = sinon.stub().returns({
generateAuthUrl: generateUrlStub,
getToken: tokenStub,
});
Code = proxyquire('../Code.js', {
'google-auth-library': mockClient,
});
});
it('should call generateAuthUrl', async function() {
const output = await Code.getRedirectUrl();
sinon.assert.called(generateUrlStub)
});
});

Here is the unit test solution:
const { OAuth2Client } = require("google-auth-library");
const keys = {
installed: {
client_id: "1",
client_secret: "client_secret",
redirect_uris: ["http://example.com/callback"]
}
};
async function getRedirectUrl() {
const oAuth2Client = new OAuth2Client(
keys.installed.client_id,
keys.installed.client_secret,
keys.installed.redirect_uris[0]
);
const authorizeUrl = oAuth2Client.generateAuthUrl({
access_type: "offline",
scope: "https://www.googleapis.com/auth/bigquery",
prompt: "consent"
});
return authorizeUrl;
}
module.exports = { getRedirectUrl };
index.spec.js:
const proxyquire = require("proxyquire");
const sinon = require("sinon");
const { expect } = require("chai");
describe("code", function() {
let generateUrlStub, tokenStub, code;
beforeEach(() => {
generateUrlStub = sinon.stub().returns("http://example.com");
tokenStub = sinon.stub().returns({ tokens: "tokens" });
code = proxyquire("./", {
"google-auth-library": {
OAuth2Client: sinon.stub().callsFake(() => {
return {
generateAuthUrl: generateUrlStub,
getToken: tokenStub
};
})
}
});
});
afterEach(() => {
sinon.restore();
});
it("should call generateAuthUrl", async function() {
const output = await code.getRedirectUrl();
expect(output).to.be.eq("http://example.com");
sinon.assert.called(generateUrlStub);
});
});
Unit test result with 100% coverage:
code
✓ should call generateAuthUrl
1 passing (216ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
index.spec.js | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/58955304

Related

How do I mock an async function that makes network request using axios?

I want to unit test a function below that calls an endpoint using axios in my node.js server.
const callValidateCookieApi = async (cookie) => {
try {
const config = {
method: 'post',
url: process.env.API_COOKIE_VALIDATION,
headers: {
Cookie: cookie
}
}
return await axios(config)
} catch (error) {
console.log(error.message)
return error
}
}
How do I write unit test cases by mocking the axios call that is inside the function?
In order to stub axios function, you need an extra package named proxyquire. For more info, see How to use Link Seams with CommonJS
Unit test solution:
index.js:
const axios = require('axios');
const callValidateCookieApi = async (cookie) => {
try {
const config = {
method: 'post',
url: process.env.API_COOKIE_VALIDATION,
headers: {
Cookie: cookie,
},
};
return await axios(config);
} catch (error) {
console.log(error.message);
return error;
}
};
module.exports = { callValidateCookieApi };
index.test.js:
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const { expect } = require('chai');
describe('64374809', () => {
it('should pass', async () => {
const axiosStub = sinon.stub().resolves('fake data');
const { callValidateCookieApi } = proxyquire('./', {
axios: axiosStub,
});
const actual = await callValidateCookieApi('sessionId');
expect(actual).to.be.eql('fake data');
sinon.assert.calledWithExactly(axiosStub, {
method: 'post',
url: undefined,
headers: {
Cookie: 'sessionId',
},
});
});
it('should handle error', async () => {
const mErr = new Error('network');
const axiosStub = sinon.stub().rejects(mErr);
const { callValidateCookieApi } = proxyquire('./', {
axios: axiosStub,
});
const actual = await callValidateCookieApi('sessionId');
expect(actual).to.be.eql(mErr);
sinon.assert.calledWithExactly(axiosStub, {
method: 'post',
url: undefined,
headers: {
Cookie: 'sessionId',
},
});
});
});
unit test result:
64374809
✓ should pass (84ms)
network
✓ should handle error
2 passing (103ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------

Sinon how to unit test double arrow function within mysql2.createPool function

I have a Connection class used to make a connection to a AWS RDS Aurora database instance. The class works fine but I'm having trouble getting full unit test coverage. There is one piece that I'm not sure how to cover. It is mysql_clear_password: () => () => Buffer.from(this.options.password + '\0') shown in the Connection class below. How can I cover that specific line? Is a refactor of the function necessary?
I have tried moving the Buffer function to a separate function, but the coverage report still shows that original line as being uncovered
Connection class:
const mysql2 = require('mysql2/promise');
class Connection {
constructor(options = {}) {
this.options = options;
}
createPool () {
this.pool = mysql2.createPool({
host: this.options.host,
user: this.options.user,
database: 'my_db',
ssl: 'Amazon RDS',
password: this.options.password,
authPlugins: {
mysql_clear_password: () => () => Buffer.from(this.options.password + '\0')
}
});
}
}
module.exports = { Connection };
Here is what I have so far in my test:
const conns = require('../src/connection');
const sinon = require('sinon');
const mysql2 = require('mysql2/promise');
describe('connection', () => {
afterEach(() => {
sinon.restore();
});
test('Test creatPool function from connection class', async () => {
const options = {
host: 'testHost',
user: 'testUser',
password: 'testPassword'
};
const createPoolStub = sinon.stub(mysql2, 'createPool').returns(sinon.stub().returnsThis());
const conn = new conns.Connection(options);
await conn.createPool();
sinon.assert.calledOnce(createPoolStub);
});
});
Using stub.callsFake method to make the stub(mysql2.createPool) call the provided function when invoked. Then, you can get the mysql_clear_password method from the provided function in your test case.
E.g.
connection.js:
const mysql2 = require('mysql2/promise');
class Connection {
constructor(options = {}) {
this.options = options;
}
createPool() {
this.pool = mysql2.createPool({
host: this.options.host,
user: this.options.user,
database: 'my_db',
ssl: 'Amazon RDS',
password: this.options.password,
authPlugins: {
mysql_clear_password: () => () => Buffer.from(this.options.password + '\0'),
},
});
}
}
module.exports = { Connection };
connection.test.js:
const mysql2 = require('mysql2/promise');
const conns = require('./connection');
const sinon = require('sinon');
const { expect } = require('chai');
describe('64300458', () => {
it('Test creatPool function from connection class', () => {
const options = {
host: 'testHost',
user: 'testUser',
password: 'testPassword',
};
let configRef;
const createPoolStub = sinon.stub(mysql2, 'createPool').callsFake((config) => {
configRef = config;
});
const conn = new conns.Connection(options);
conn.createPool();
sinon.assert.calledOnce(createPoolStub);
// test mysql_clear_password
const actual = configRef.authPlugins.mysql_clear_password()();
expect(actual).to.be.eql(Buffer.from('testPassword\0'));
createPoolStub.restore();
});
});
unit test result with coverage report:
64300458
✓ Test creatPool function from connection class
1 passing (11ms)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 100 | 0 | 100 | 100 |
connection.js | 100 | 0 | 100 | 100 | 4
---------------|---------|----------|---------|---------|-------------------

How does one properly to unit test Joi Schemas validation?

I created a Joi validation schema that gets called in my routes. However when I run a code coverage that file is NOT being covered. So, I am trying to write a test for it.
Validator.js
const Joi = require('joi');
module.exports = {
validateExternalId: (schema, name) => {
return (req, res, next) => {
const result = Joi.validate({ param: req.params[name] }, schema);
if (result.error) {
return res.status(400).send(result.error.details[0].message);
}
next();
};
},
schemas: {
idSchema: Joi.object().keys({
param: Joi.string().regex(/^[a-zA-Z0-9]{20}$/).required()
})
}
};
Validator.test.js
const { validateExternalId, schemas } = require('../../src/helpers/validation');
const app = require('../../src/router')
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
describe('Testing validateExternalId schema', () => {
it('It can validate the external Id Regex length', done => {
const req = {
params: [
{
extClientId: 'abcdefghij0123456789'
}
]
};
app.use('/token/:extClientId', validateExternalId(schemas.idSchema, 'extClientId');
// expect().toHaveBeenCalled();
});
});
Please Go Easy on ME... Here is my attempt on testing this Joi validator. I tried to but my expected wasn't working so I commented it out for now. any pointers would be appreciated. thank you
Here is the unit test solution:
validator.js:
const Joi = require('joi');
module.exports = {
validateExternalId: (schema, name) => {
return (req, res, next) => {
const result = Joi.validate({ param: req.params[name] }, schema);
if (result.error) {
return res.status(400).send(result.error.details[0].message);
}
next();
};
},
schemas: {
idSchema: Joi.object().keys({
param: Joi.string()
.regex(/^[a-zA-Z0-9]{20}$/)
.required(),
}),
},
};
validator.test.js:
const { validateExternalId, schemas } = require('./validator');
const Joi = require('joi');
describe('60730701', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('should send error', () => {
const validationResults = { error: { details: [{ message: 'validation error' }] } };
const validateSpy = jest.spyOn(Joi, 'validate').mockReturnValueOnce(validationResults);
const mReq = { params: { extClientId: '123' } };
const mRes = { status: jest.fn().mockReturnThis(), send: jest.fn() };
validateExternalId(schemas.idSchema, 'extClientId')(mReq, mRes);
expect(validateSpy).toBeCalledWith({ param: '123' }, schemas.idSchema);
expect(mRes.status).toBeCalledWith(400);
expect(mRes.send).toBeCalledWith('validation error');
});
it('should pass the validation and call api', () => {
const validationResults = { error: undefined };
const validateSpy = jest.spyOn(Joi, 'validate').mockReturnValueOnce(validationResults);
const mReq = { params: { extClientId: '123' } };
const mRes = {};
const mNext = jest.fn();
validateExternalId(schemas.idSchema, 'extClientId')(mReq, mRes, mNext);
expect(validateSpy).toBeCalledWith({ param: '123' }, schemas.idSchema);
expect(mNext).toBeCalled();
});
});
unit test results with 100% coverage:
PASS stackoverflow/60730701/validator.test.js (9.96s)
60730701
✓ should send error (6ms)
✓ should pass the validation and call api (2ms)
--------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
validator.js | 100 | 100 | 100 | 100 |
--------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 11.647s, estimated 15s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60730701

Unit testing events with mocha

Not sure where to start or what is the best approach to testing the following code with mocha and chai:
module.exports = function trackCustomDyEvents(events){
let $;
trackEvents();
function trackEvents() {
events.forEach((event) => {
$(document).on(event.type, event.element, () => {
if (!event.predicate()) return;
window.DY.API('event', {
name: event.name
});
});
});
}
};
Here is the unit test solution, you need a stub library such as sinon.js and I assume you use jquery.
index.js:
window.DY = window.DY || {};
window.DY.API = function(event, obj) {
// Your real implementation
};
module.exports = function trackCustomDyEvents(events) {
const $ = require("jquery");
trackEvents();
function trackEvents() {
events.forEach((event) => {
$(document).on(event.type, event.element, () => {
if (!event.predicate()) return;
window.DY.API("event", {
name: event.name,
});
});
});
}
};
index.test.js:
const jsdom = require("jsdom");
const sinon = require("sinon");
const proxyquire = require("proxyquire");
describe("trackCustomDyEvents", () => {
before(() => {
const html = '<!doctype html><html><head><meta charset="utf-8">' + "</head><body></body></html>";
const url = "http://localhost";
const document = new jsdom.JSDOM(html, { url });
const window = document.window;
global.document = window.document;
global.window = window;
});
afterEach(() => {
sinon.restore();
});
it("should call DY.API", () => {
const onStub = sinon.stub().yields();
const jqueryStub = sinon.stub().callsFake(() => ({
on: onStub,
}));
const trackCustomDyEvents = proxyquire("./", {
jquery: jqueryStub,
});
const APISpy = sinon.stub(window.DY, "API");
const predicateStub = sinon.stub().returns(true);
const events = [
{ type: "click", element: "button", name: "a", predicate: predicateStub },
{ type: "submit", element: "form", name: "b", predicate: predicateStub },
];
trackCustomDyEvents(events);
sinon.assert.calledWithExactly(onStub.firstCall, "click", "button", sinon.match.func);
sinon.assert.calledWithExactly(onStub.secondCall, "submit", "form", sinon.match.func);
sinon.assert.calledTwice(predicateStub);
sinon.assert.calledWithExactly(APISpy.firstCall, "event", { name: "a" });
sinon.assert.calledWithExactly(APISpy.secondCall, "event", { name: "b" });
});
it("should not call DY.API", () => {
const onStub = sinon.stub().yields();
const jqueryStub = sinon.stub().callsFake(() => ({
on: onStub,
}));
const trackCustomDyEvents = proxyquire("./", {
jquery: jqueryStub,
});
const APISpy = sinon.stub(window.DY, "API");
const predicateStub = sinon.stub().returns(false);
const events = [
{ type: "click", element: "button", name: "a", predicate: predicateStub },
{ type: "submit", element: "form", name: "b", predicate: predicateStub },
];
trackCustomDyEvents(events);
sinon.assert.calledWithExactly(onStub.firstCall, "click", "button", sinon.match.func);
sinon.assert.calledWithExactly(onStub.secondCall, "submit", "form", sinon.match.func);
sinon.assert.calledTwice(predicateStub);
sinon.assert.notCalled(APISpy);
});
});
Unit test result with 100% coverage:
trackCustomDyEvents
✓ should call DY.API (76ms)
✓ should not call DY.API
2 passing (146ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 91.67 | 100 | |
index.js | 100 | 100 | 80 | 100 | |
index.test.js | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/59569112

Write a unit test in mocha for the given method using mocks/stubs

I am pretty new to unit testing in node.js.
I have a function which looks something like this. I need to write a unit test for the function.
async refreshToken(req, res) {
const token = req.body.token || req.query.token;
const options = {method: 'POST', url: ''};
let resp;
try {
resp = await request(options);
} catch (e) {
console.error(e);
}
if (resp) {
const grant = {
another_token: {
token: resp.another_token,
},
expires_in: resp.expires_in
}
res.end(JSON.stringify(grant));
} else {
res.status(400).end("not authorized");
}
}
I am planning to use mocha framework with sinon and chai.
I tried a test using mocha, but could not figure out how to assert if the request(options) is called at least once.
describe('refreshToken', () => {
it('should take token from body', async () => {
const req = {
body: {
token: "123"
}
}
await auth.refreshToken(req, res);
request.should.have.been.calledOnceWithExactly(options);
})
I get:
TypeError: Cannot read property 'xxxxx' of undefined
I am having difficulty how to make this work with mock/stub.
Here is the unit test solution:
auth.ts:
import request from 'request-promise';
export async function refreshToken(req, res) {
const token = req.body.token || req.query.token;
const options = { method: 'POST', url: 'https://github.com/mrdulin' };
let resp;
try {
resp = await request(options);
} catch (e) {
console.error(e);
}
if (resp) {
const grant = {
another_token: {
token: resp.another_token
},
expires_in: resp.expires_in
};
res.end(JSON.stringify(grant));
} else {
res.status(400).end('not authorized');
}
}
auth.spec.ts:
import sinon from 'sinon';
import proxyquire from 'proxyquire';
import { expect } from 'chai';
describe('auth', () => {
describe('#refreshToken', () => {
it('should take token from body', async () => {
const mResponse = { another_token: '123', expires_in: '3600' };
const requestPromiseStub = sinon.stub().resolves(mResponse);
const auth = proxyquire('./auth', {
'request-promise': requestPromiseStub
});
const req = {
body: {
token: '123'
}
};
const res = { end: sinon.stub() };
await auth.refreshToken(req, res);
expect(requestPromiseStub.calledWith({ method: 'POST', url: 'https://github.com/mrdulin' })).to.be.true;
expect(
res.end.calledWith(
JSON.stringify({
another_token: {
token: mResponse.another_token
},
expires_in: mResponse.expires_in
})
)
).to.be.true;
});
it('should cause not authorized error', async () => {
const mError = new Error('network error');
const requestPromiseStub = sinon.stub().rejects(mError);
const auth = proxyquire('./auth', {
'request-promise': requestPromiseStub
});
const errorLogSpy = sinon.spy(console, 'error');
const req = {
body: {
token: '123'
}
};
const res = { status: sinon.stub().returnsThis(), end: sinon.stub() };
await auth.refreshToken(req, res);
expect(errorLogSpy.calledWith(mError)).to.be.true;
expect(res.status.calledWith(400)).to.be.true;
expect(res.status().end.calledWith('not authorized')).to.be.true;
});
});
});
Unit test result with coverage report:
auth
#refreshToken
✓ should take token from body (1262ms)
Error: network error
at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:26462
at step (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:23604)
at Object.next (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:20644)
at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:19788
at new Promise (<anonymous>)
at __awaiter (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:18995)
at Context.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:26156)
at callFn (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runnable.js:387:21)
at Test.Runnable.run (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runnable.js:379:7)
at Runner.runTest (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:535:10)
at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:653:12
at next (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:447:14)
at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:457:7
at next (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:362:14)
at Immediate.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:425:5)
at runCallback (timers.js:705:18)
at tryOnImmediate (timers.js:676:5)
at processImmediate (timers.js:658:5)
✓ should cause not authorized error (88ms)
2 passing (1s)
--------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------|----------|----------|----------|----------|-------------------|
All files | 100 | 75 | 100 | 100 | |
auth.spec.ts | 100 | 100 | 100 | 100 | |
auth.ts | 100 | 75 | 100 | 100 | 4 |
--------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/57479631

Resources