I am trying to stub the getDB function within the Connection class , which is a singleton.
When i am making a unit test on Class B it works fine.
Now i am trying to create a component test, which means using Class A and stub the getDB function again (Class A uses Class B which uses the Connection class - getDb).
But the stubbing doesn't work.
Not sure why.
Here are the classes:
Class Connection
"use strict";
const config = {};
const pgp = require("pg-promise")();
const db = pgp(config);
class Connection {
constructor() {
console.log("Connection ctor")
}
getDb() {
console.log("getDb")
return db;
}
}
module.exports = new Connection();
Class B
"use strict";
class B {
constructor() {
console.log("B ctor")
this.db = require("./Connection").getDb();
}
async getDataFromDb() {
console.log("B getDataFromDb")
let res = null;
res = await this.db.one("SELECT guid FROM table", ["NONE"]);
return res.guid;
}
}
module.exports = B;
Class A
"use strict";
const bDao = new (require("../db/B"))();
class A {
async getGuid() {
console.log("A getGuid")
let guid = await bDao.getDataFromDb();
console.log("got guid " + guid);
return guid;
}
}
module.exports = A;
The Test - the B_unit_test works ,
but when i try to stub the getDb from the connection class, it doesn't stub it and the component_test fails.
const chai = require("chai");
const expect = chai.expect;
const A = require("../src/service/A");
const Connection = require("../src/db/Connection");
const B = require("../src/db/B");
const sinon = require("sinon");
describe("component_test", function () {
before(() => {
var db = {
one: async () => {
return {
guid: '24f475b6-f9fa-495b-83f4-16f6b45b0a6a',
created_on: "123"
}
},
none: async () => {
}
};
this.dbStub = sinon.stub(Connection, "getDb").returns(db);
});
after(() => {
this.dbStub.restore();
});
it("B_unit_test", async function () {
var dao = new B();
var guid = await dao.getDataFromDb();
expect(guid).to.equal('24f475b6-f9fa-495b-83f4-16f6b45b0a6a');
});
it("A_component_test", async function () {
var dao = new A();
var guid = await dao.getGuid();
expect(guid).to.equal('24f475b6-f9fa-495b-83f4-16f6b45b0a6a');
});
});
You'd better stub getDataFromDb of B which is the class that A used directly. The unit test solution:
connection.js:
const config = {};
const pgp = require('pg-promise')();
const db = pgp(config);
class Connection {
constructor() {
console.log('Connection ctor');
}
getDb() {
console.log('getDb');
return db;
}
}
module.exports = new Connection();
b.js:
class B {
constructor() {
console.log('B ctor');
this.db = require('./connection').getDb();
}
async getDataFromDb() {
console.log('B getDataFromDb');
let res = null;
res = await this.db.one('SELECT guid FROM table', ['NONE']);
return res.guid;
}
}
module.exports = B;
b.test.js:
class B {
constructor() {
console.log('B ctor');
this.db = require('./connection').getDb();
}
async getDataFromDb() {
console.log('B getDataFromDb');
let res = null;
res = await this.db.one('SELECT guid FROM table', ['NONE']);
return res.guid;
}
}
module.exports = B;
a.js:
const bDao = new (require('./b'))();
class A {
async getGuid() {
console.log('A getGuid');
let guid = await bDao.getDataFromDb();
console.log('got guid ' + guid);
return guid;
}
}
module.exports = A;
a.test.js:
const B = require('./b');
const sinon = require('sinon');
const { expect } = require('chai');
describe('a', () => {
it('should pass', async () => {
const getDataFromDbStub = sinon.stub(B.prototype, 'getDataFromDb').resolves('24f475b6-f9fa-495b-83f4-16f6b45b0a6a');
const A = require('./a');
const aDao = new A();
const guid = await aDao.getGuid();
expect(guid).to.be.equal('24f475b6-f9fa-495b-83f4-16f6b45b0a6a');
sinon.assert.calledOnce(getDataFromDbStub);
});
});
unit test result with coverage report:
a
B ctor
getDb
A getGuid
got guid 24f475b6-f9fa-495b-83f4-16f6b45b0a6a
✓ should pass (47ms)
b
B ctor
B getDataFromDb
✓ should pass
2 passing (65ms)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
a.js | 100 | 100 | 100 | 100 |
b.js | 100 | 100 | 100 | 100 |
connection.js | 100 | 100 | 100 | 100 |
---------------|---------|----------|---------|---------|-------------------
Related
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
----------|---------|----------|---------|---------|-------------------
I am having a function, where I am doing some db operations. Like,
const { Pool } = require("pg");
const pool = new Pool({
connectionString: `some connection string...`,
});
var fun = async function (pk_name, pk_value) {
try {
const db = await pool.connect();
const query = `SELECT *
FROM creds
WHERE ${pk_name} = $1`;
var res = await db.query(query, [pk_value]);
db.release();
return res.rows;
} catch (ex) {
return [];
}
};
module.exports.isValidUser = async function (pk_name, pk_value, password) {
try {
var userData = await fun(pk_name, pk_value);
return userData[0].email === pk_value && userData[0].password === password;
} catch (ex) {
return false;
}
};
and I am trying to mock the above methods like pool.connect() ,db.query() and db.release()
so, I tried following things
var sinon = require("sinon");
var assert = sinon.assert;
const { Pool } = require("pg");
const pool = new Pool()
var databaseControllerTestPositive = function () {
it("valid user check test", async function () {
sinon.stub(logUtils, "info");
sinon.stub(pool, 'connect')
sinon.stub(pool, 'query').returns({
email: "demo#gmail.com",
password: "demo"
})
sinon.stub(pool.prototype.connect, "release");
// isValidUser is another function which calls the above fun() internally.
var result = await dbUtils.isValidUser(
"fake_pk_name",
"fake_pk_value",
"pass"
);
assert.match(result, { rows: [] });
});
};
But, the above test is failing and the methods which I was trying to mock, is not really getting mocked.
I have found a similar question, but I didn't find it's answer so helpful.
Could anyone please help me, if I am doing anything wrong.
You can use Link Seams, so we need a extra package - proxyquire. Since the fun function is not exported, we can't require it and test it directly. We need to test it together with isValidUser function.
E.g.
main.js:
const { Pool } = require('pg');
const pool = new Pool({
connectionString: `some connection string...`,
});
var fun = async function (pk_name, pk_value) {
try {
const db = await pool.connect();
const query = `SELECT *
FROM creds
WHERE ${pk_name} = $1`;
var res = await db.query(query, [pk_value]);
db.release();
return res.rows;
} catch (ex) {
return [];
}
};
module.exports.isValidUser = async function (pk_name, pk_value, password) {
try {
var userData = await fun(pk_name, pk_value);
return userData[0].email === pk_value && userData[0].password === password;
} catch (ex) {
return false;
}
};
main.test.js:
const proxyquire = require('proxyquire');
const sinon = require('sinon');
var assert = sinon.assert;
describe('65940805', () => {
it('valid user check test', async function () {
const res = { rows: [{ email: 'fake_pk_value', password: 'pass' }] };
const dbStub = {
query: sinon.stub().resolves(res),
release: sinon.stub(),
};
const poolInstanceStub = {
connect: sinon.stub().resolves(dbStub),
};
const PoolStub = sinon.stub().returns(poolInstanceStub);
const dbUtils = proxyquire('./main', {
pg: {
Pool: PoolStub,
},
});
var result = await dbUtils.isValidUser('fake_pk_name', 'fake_pk_value', 'pass');
assert.match(result, true);
sinon.assert.calledWithExactly(PoolStub, {
connectionString: `some connection string...`,
});
sinon.assert.calledOnce(poolInstanceStub.connect);
sinon.assert.calledWithExactly(dbStub.query, sinon.match.string, ['fake_pk_value']);
sinon.assert.calledOnce(dbStub.release);
});
});
unit test result:
65940805
√ valid user check test (1013ms)
1 passing (1s)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 86.67 | 100 | 100 | 86.67 |
main.js | 86.67 | 100 | 100 | 86.67 | 17,26
----------|---------|----------|---------|---------|-------------------
I have 2 tests which each one them pass but when i run them together the second test fails.
I have this global var (CURR_GUID) which i want to clear between the tests but i am not sure how to do that.
Since it is not cleared between the tests , its value is still "1111111" and not null.
Any idea?
Class W
"use strict";
var CURR_GUID = null;
class W {
constructor() {
console.log("W ctor")
}
async getDataFromDb() {
if (CURR_GUID) {
return CURR_GUID;
} else {
this.db = require("./Connection").getDb();
console.log("W getDataFromDb")
let res = null;
res = await this.db.one("SELECT guid FROM table", ["NONE"]);
return res.guid;
}
}
async saveGuid(guid) {
this.db = require("./Connection").getDb();
await this.db.none("update table set guid=$1", [guid]);
CURR_GUID = guid;
console.log("currr is" + CURR_GUID);
}
}
module.exports = W;
Class X
"use strict";
const wDao = new (require("../db/W"))();
class X {
async getGuid() {
console.log("X getGuid")
let guid = await wDao.getDataFromDb();
console.log("got guid " + guid);
await wDao.saveGuid(guid);
return guid;
}
}
module.exports = X;
Test.spec.js
const chai = require("chai");
const expect = chai.expect;
const X = require("../src/service/X");
const Connection = require("../src/db/Connection");
const sinon = require("sinon");
describe("component_test", function () {
afterEach(function () {
this.dbStub.restore();
});
it("X_component_test", async function () {
var db = {
one: async () => {
return {
guid: '1111111',
created_on: "123"
}
},
none: async () => {
}
};
this.dbStub = sinon.stub(Connection, "getDb").returns(db);
var dao = new X();
var guid = await dao.getGuid();
expect(guid).to.equal('1111111');
});
it("X_component_test_2", async function () {
var db = {
one: async () => {
return {
guid: '222222',
created_on: "123"
}
},
none: async () => {
}
};
this.dbStub = sinon.stub(Connection, "getDb").returns(db);
var dao = new X();
var guid = await dao.getGuid();
expect(guid).to.equal('222222');
});
});
While it should be possible to achieve the desired result by deleting W and X modules from require.cache in afterEach hook, it might be better to modify the source code a bit so that it can be tested without cache manipulations.
Class W
Turn CURR_GUID variable into an instance property.
"use strict";
class W {
constructor() {
console.log("W ctor");
this.CURR_GUID = null;
}
async getDataFromDb() {
if (this.CURR_GUID) {
return this.CURR_GUID;
} else {
this.db = require("./Connection").getDb();
console.log("W getDataFromDb")
let res = null;
res = await this.db.one("SELECT guid FROM table", ["NONE"]);
return res.guid;
}
}
async saveGuid(guid) {
this.db = require("./Connection").getDb();
await this.db.none("update table set guid=$1", [guid]);
this.CURR_GUID = guid;
console.log("currr is" + this.CURR_GUID);
}
}
module.exports = W;
Class X
Turn wDao from variable into an instance property
"use strict";
const WDao = require("../db/W");
class X {
constructor() {
this.wDao = new WDao();
}
async getGuid() {
console.log("X getGuid")
let guid = await this.wDao.getDataFromDb();
console.log("got guid " + guid);
await this.wDao.saveGuid(guid);
return guid;
}
}
module.exports = X;
In this way every test should work with fresh instances of W and X that do not depend on the closure state.
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
I have a class named ReportHandler that depends on other classes (ReportService, S3Handlere) like this one:
S3Handler: a class for downloading, uploading file to S3
ReportService: Doing CRUD to database
handleReportAsync is a function that download file from S3, do some logic and update file to database.
export class ReportHandler {
private s3Handler;
private reportService;
public constructor(reportService) {
this.reportService = reportService;;
const accessKey = process.env.AWS_ACCESS_KEY;
const secretKey = process.env.AWS_SECRET_ACCESS_KEY;
const bucketName = process.env.AWS_BUCKET_NAME;
this.s3Handler = new S3Handler(accessKey, secretKey, bucketName);
}
public async handleReportAsync(report) {
try {
const data = await this.s3Handler.downloadFile(report.id);
// do some logic
reportService.updateFile(report.id, data.Body);
} catch (error) {
}
}
}
I want to test whether the reportService.updateFile() is called or not, so I'm going to use spy for this task.
And obviously, I don't want to download real file from S3 so, How can I stub the this.s3Handler.downloadFile() function by Sinon.js.
This is my try but without success.
describe("ReportHandler", () => {
describe("handleReportAsync", () => {
let sandbox;
beforeEach(() => {
sandbox = Sinon.createSandbox();
});
afterEach(() => {
sandbox.restore();
});
it("should call updateFile() function", async () => {
const report = new Report(faker.random.uuid());
sandbox.stub(S3Handler.prototype, "downloadFile").callsFake(
(id) => {},
);
sandbox.stub(ReportService.prototype, "updateFile").callsFake(
(id, file) => {},
);
const reportHandler = new ReportHandler(new ReportService());
const spy = Sinon.spy(ReportService.prototype, "updateFile");
await reportHandler.handleReportAsync(report);
expect(spy.called).to.be.true;
});
});
});
Any advice is welcome! Thanks in advance.
Here is the unit test solution:
reportHandler.ts:
import S3Handler from "./s3Handler";
export class ReportHandler {
private s3Handler;
private reportService;
public constructor(reportService) {
this.reportService = reportService;
const accessKey = process.env.AWS_ACCESS_KEY;
const secretKey = process.env.AWS_SECRET_ACCESS_KEY;
const bucketName = process.env.AWS_BUCKET_NAME;
this.s3Handler = new S3Handler(accessKey, secretKey, bucketName);
}
public async handleReportAsync(report) {
try {
const data = await this.s3Handler.downloadFile(report.id);
this.reportService.updateFile(report.id, data.Body);
} catch (error) {}
}
}
s3Handler.ts:
export default class S3Handler {
constructor(accessKey, secretKey, bucketName) {}
}
reportHandler.test.ts:
import { ReportHandler } from "./reportHandler";
import sinon from "sinon";
describe("59766312", () => {
let sandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
});
afterEach(() => {
sandbox.restore();
});
describe("#handleReportAsync", () => {
it("should update file", async () => {
const reportServiceStub = { updateFile: sandbox.stub() };
const s3Handler = require("./s3Handler");
const downloadFileStub = sandbox.stub().resolves({ Body: "a" });
const s3HandlerInstanceStub = { downloadFile: downloadFileStub };
const s3HandlerStub = sandbox.stub(s3Handler, "default").callsFake(() => s3HandlerInstanceStub);
const reportHandler = new ReportHandler(reportServiceStub);
const report = { id: 1 };
await reportHandler.handleReportAsync(report);
sandbox.assert.calledOnce(s3HandlerStub);
sandbox.assert.calledWithExactly(s3HandlerInstanceStub.downloadFile, 1);
sandbox.assert.calledWithExactly(reportServiceStub.updateFile, 1, "a");
});
});
});
Unit test results with coverage report:
59766312
#handleReportAsync
✓ should update file
1 passing (12ms)
-----------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 90.91 | 100 | |
reportHandler.test.ts | 100 | 100 | 100 | 100 | |
reportHandler.ts | 100 | 100 | 100 | 100 | |
s3Handler.ts | 100 | 100 | 50 | 100 | |
-----------------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/59766312