Mock shell command output in Jest - javascript

I'm writing a cli tool and I'm trying to write tests for it in Jest. I have some functions that call out to git, but I need to mock the returns from those calls or they aren't going to be consistent.
The code I'm using to call out to the shell looks like this.
import { exec } from "child_process";
function execute(command) {
return new Promise((resolve, reject) => {
exec(command, resolve);
});
}
export const getGitDiff = function () {
return execute("git diff")
};
How can I write a test for that in Jest?
What I tried was
import { getGitDiff } from './getGitDiff';
describe('get git diff', () => {
it('should send "git diff" to stdin', () => {
const spy = jest.spyOn(process.stdin, 'write');
return getGitDiff().then(() => {
expect(spy).toHaveBeenCalled();
})
});
});

I ended up creating a new file called child_process.js and using the genMockFromModule functionality in Jest to stub the whole module and reimplemented some of the functions like this
const child_process = jest.genMockFromModule('child_process');
const mockOutput = {}
const exec = jest.fn().mockImplementation((command, resolve) => {
resolve(mockOutput[command]);
})
const __setResponse = (command, string) => {
mockOutput[command] = string;
}
child_process.exec = exec
child_process.__setResponse = __setResponse;
module.exports = child_process;
and I have a test like
const child_process = jest.genMockFromModule('child_process');
const mockOutput = {}
const exec = jest.fn().mockImplementation((command, resolve) => {
resolve(mockOutput[command]);
})
const __setResponse = (command, string) => {
mockOutput[command] = string;
}
child_process.exec = exec
child_process.__setResponse = __setResponse;
module.exports = child_process;

Related

How do I implement a unit test for a function that invokes two other functions

Inside this module, I want to test the getAppts function, which invokes two function. What is the correct way of evaluating the code that getAppts encompasses? Do I have to run db.getDatabase() and fetchAppts() as stubs inside the unit test function? My unit test implementation is, at best, incomplete, but it could be totally wrong.
'use strict'
const db = require('../db/db')
const log = require('../utils/logging')(__filename)
const { fetchAppts, fetchApptDetails } = require('../db/dao/appointment-dao')
async function getAppts (queryParams) {
log.debug('getAppts(): %j', queryParams)
const knex = db.getDatabase()
const appointments = await fetchAppts(queryParams, knex)
return appointments
}
async function getApptDetails (demoApptId) {
log.debug('getApptDetails(): %j', demoApptId)
const knex = db.getDatabase()
const apptDetails = await fetchApptDetails(demoApptId, knex)
return apptDetails
}
module.exports = {
getAppts,
getApptDetails
}
unit test setup:
'use strict'
const sinon = require('sinon')
const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
const dirtyChai = require('dirty-chai')
const sinonChai = require('sinon-chai')
const { expect } = require('chai')
const proxyquire = require('proxyquire')
const apptsResponse = require('../../data/get-appts-response.json')
chai.use(dirtyChai)
chai.use(sinonChai)
chai.use(chaiAsPromised)
chai.should()
describe.only('appointment-service.js', () => {
let apptService
let apptDaoStub
let dbStub
beforeEach(() => {
dbStub = {
getDatabase: sinon.stub()
}
apptDaoStub = {
fetchAppts: sinon.stub()
}
apptService = proxyquire('../../../services/appointment-service', {
'../db/db': dbStub,
'../db/dao/appointment-dao': apptDaoStub
})
})
afterEach(() => sinon.restore())
describe('getAppts', () => {
it.only('should get appointments', async () => {
const appts = await apptService.getAppts({})
dbStub.getDatabase.should.have.been.calledTwice
})
})
})
You can use mock functions, you will need to import the module:
it('should get appointments', async () => {
const mockFn = jest.spyOn(db, 'getDatabase');
const appts = await apptService.getAppts({})
expect(mockFn).toBeCalledTimes(1); // depends on how many times fn is called
});
You can learn more about mocking here:
https://www.chakshunyu.com/blog/how-to-mock-only-one-function-from-a-module-in-jest/

How to mock a callback function from bookshelf js using sinnon

I would like to mock this piece of code that is using bookshelf js (with knex) with sinon.
const campaigns = await models.Campaign.forge()
.query((qb) => {
qb.where("account_id", accountId);
qb.andWhere("status", models.Campaign.STATUS.ACTIVE);
qb.andWhere(
"audience_create_trigger",
models.Campaign.AUDIENCE_CREATE_TRIGGER.ON_ENTER
);
})
.fetchAll();
How could I mock the inner queries inside the .query function.
I am a bit lost
Thank you very much!
I finally solved the problem using Sinon. This code is covering almost all the behavior
const assert = require("chai").assert
const sinon = require("sinon")
const models = require("../../../../models")
const query = {
query(func) {}
}
const qb = {
where(arg1, arg2) {},
andWhere(arg1, arg2) {}
}
const fetchAll = { async fetchAll() {} }
const forgeStub = sinon.stub(models.Campaign, "forge").returns(query)
const qbWhereStub = sinon
.stub(qb, "where")
.withArgs("account_id", accountId)
.returns(null)
const qbAndWhereStub = sinon.stub(qb, "andWhere").returns(null)
const queryStub = sandbox
.stub(query, "query")
.callsArgWith(0, qb)
.returns(fetchAll)
const fetchAllStub = sandbox.stub(fetchAll, "fetchAll").returns(campaigs)
//Calling the method
//Verify
assert.equal(qbWhereStub.callCount, 1)
assert.equal(qbAndWhereStub.callCount, 2)
assert.equal(forgeStub.callCount, 1)
assert.equal(queryStub.callCount, 1)
assert.equal(fetchAllStub.callCount, 1)
assert.isTrue(qbWhereStub.getCall(0).calledWithExactly("account_id", accountId))
assert.isTrue(qbAndWhereStub.getCall(0).calledWithExactly("status", models.Campaign.STATUS.ACTIVE))
assert.isTrue(
qbAndWhereStub
.getCall(1)
.calledWithExactly("audience_create_trigger", models.Campaign.AUDIENCE_CREATE_TRIGGER.ON_ENTER)
)
Try to use knex-mock-client to setup a test friendly knex instance.
The test will look much simpler.
import knex from "knex";
import { MockClient, getTracker } from "knex-mock-client";
describe("test", () => {
let db;
let tracker;
beforeAll(() => {
db = knex({ client: MockClient });
tracker = getTracker();
});
afterEach(() => tracker.reset());
it("should do something", () => {
tracker.on
.select(
(query) =>
query.sql.includes("table_name") && query.bindings.includes(accountId)
)
.responseOnce([]);
// execute query using db;
expect(tracker.history.select).toHaveLength(1);
});
});

Unit testing an inner function?

I have a function that has inner functions, for my unit test, I only want to test the functionality of the inner function, but when I export the function and call the inner function, npm tests returns an error.
In my main.js:
mainFunction = () => {
functionToBeTested = () => {
// some code
}
}
module.exports = {mainFunction: mainFunction}
In my test.js
const chai = require("chai");
const assert = require("chai").assert;
const mainFunction = require("./main");
describe ("test", () => {
it("returns results", () => {
let result = mainfunction.functionToBeTested(args);
//equal code
});
})
But when I run npm test, it says:
mainfunction.functionToBeTested is not a function.
What am I doing wrong?
If you want to chain your functions you can try something like that.
main.js
const mainFunction = () => {
const functionToBeTested = () => {
return "I got it";
}
return { functionToBeTested };
}
module.exports = { mainFunction };
test.js
const chai = require("chai");
const assert = require("chai").assert;
const mainFunction = require("./main");
const mf = mainFunction();
describe ("test", () => {
it("returns results", () => {
let result = mf.functionToBeTested(args);
//equal code
});
});
Actually, you can't call a function declare inside another function that way. A solution would be to declare functionToBeTested outside mainFunction, then call it :
main.js
const functionToBeTested = () => {
// some code
};
const mainFunction = () => {
functionToBeTested();
};
module.exports = { mainFunction, functionToBeTested }
test.js
const chai = require("chai");
const assert = require("chai").assert;
const { mainFunction, functionToBeTested } = require("./main");
describe ("test", () => {
it("tests mainFunction", () => {
let main = mainfunction(args);
...
});
it("tests functionToBeTested"), () => {
let tested = functionToBeTested(args);
...
});
})
It is because only mainFunction() is exported and not the functionToBeTested(), outside this module JS doesn't knows about the existence of the functionToBeTested().
I will recommend you to move functionToBeTested separated and export that as well or have a helper method for calling it.

Node.js - unit test for function with mocked promise inside

I'm currently making a small server in JavaScript and as part of the learning process I'm writing unit tests for the functions. Unfortunately I ran into major difficulties with a certain test that handles a promise. Below is the router module, with a separate handlePUT function for ease of testing.
const express = require('express');
const service = require('../service/user.service');
const dutyStatusRouter = express.Router();
const output = console;
function handlePUT(req, res) {
service.updateUserStatus()
.then((fulfilled) => {
res.status(fulfilled);
res.send();
})
.catch(() => {
res.status(500);
res.send();
});
}
dutyStatusRouter.route('/').put(handlePUT);
The updateUserStatus function basically toggles a Boolean in a database and looks somewhat like this:
function updateUserStatus() {
return new Promise((resolve, reject) => {
if (…) {
resolve(201);
} else if (…) {
resolve(200);
} else {
reject();
}
});
}
As for the unit tests, I'm using mocha/chai, with proxyquire to create a mock updateUserStatus.
const chai = require('chai');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
const serviceStub = {};
describe('=== Unit test ===', () => {
it('Handle PUT test: promise kept', async () => {
const dutyStatusRouter = proxyquire('../../router/duty-status.router', {
'../service/user.service': serviceStub,
});
serviceStub.updateUserStatus = () => {
return new Promise((resolve, reject) => {
resolve(200);
});
};
const res = {
status: sinon.fake(),
send: sinon.fake(),
};
await dutyStatusRouter.handlePUT({}, res);
chai.assert(res.status.calledOnceWith(200));
});
});
Whenever I try to run the unit test, I get the error Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.. If I try to add done() it still fails by giving the error message Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both.
Found a solution that works, so I'm adding it here:
const chai = require('chai');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
const serviceStub = {};
const dutyStatusRouter = proxyquire('../../router/duty-status.router', {
'../service/user.service': serviceStub,
});
describe('=== Unit test ===', () => {
it('Handle PUT test: promise kept', (done) => {
serviceStub.updateUserStatus = sinon.stub().resolves(200);
const res = {
status: sinon.fake(),
send: sinon.fake(),
};
dutyStatusRouter.handlePUT({}, res).then(() => {
chai.assert(res.status.calledWith(200));
done();
});
});
});
Note: I changed the handlePUT function just a bit, it now looks like this (I just added a return):
function handlePUT(req, res) {
return service.updateUserStatus()
.then((fulfilled) => {
output.log('Promise fulfilled');
res.status(fulfilled);
res.send();
})
.catch(() => {
output.log('Promise unfulfilled');
res.status(500);
res.send();
});
}

node.js - Apply sinon on mongodb unit tests

I implemented a model function for mongodb with node-mongodb-native:
'use strict';
const mongo = require('mongodb');
class BlacklistModel {
constructor(db, tenant_id, logger) {
this._db = db;
this._table = 'blacklist_' + tenant_id;
this._logger = logger;
}
create(data) {
return new Promise((resolve, reject) => {
const options = {
unique: true,
background: true,
w: 1
};
this._db.collection(this._table).ensureIndex({ phone: 1 }, options, (err) => {
if (err) {
this._logger.error(err);
reject(err);
} else {
const datetime = Date.parse(new Date());
data._id = new mongo.ObjectID().toString();
data.createdAt = datetime;
data.updatedAt = datetime;
this._db.collection(this._table).insertOne(data, (err) => {
if (err) {
this._logger.error(err);
reject(err);
} else {
resolve(data);
}
});
}
});
});
}
}
module.exports = BlacklistModel;
Now I want to write unit tests for it, considering 3 cases:
Successful insertion
Fail due to violating unique index
Fail due to lost connection
Bearing those in minds, here are my tests:
'use strict';
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
const BlacklistModel = require('../../model/blacklist');
const mongo_url = require('../../config/mongodb');
const MongoClient = require('mongodb').MongoClient;
const logger = require('../../config/logger');
const data = {
name: 'admin'
};
describe('Model: Blacklist', () => {
let Blacklist;
let connected = false;
let test_db;
const connect = () => new Promise((resolve, reject) => {
MongoClient.connect(mongo_url, (err, db) => {
if (err) {
reject(err);
} else {
Blacklist = new BlacklistModel(db, 'test', logger);
connected = true;
test_db = db;
resolve();
}
});
});
before(() => connect());
describe('create', () => {
let id;
beforeEach(() => connected ?
null : connect());
it('Should return an inserted document', () => {
return Blacklist.create(data).then(
(result) => {
expect(result._id).to.be.a('string');
expect(result.name).to.equal(data.name);
expect(result.createdAt).to.be.a('number');
expect(result.updatedAt).to.be.a('number');
id = result._id;
});
});
it('Should fail to insert a blacklist with the same name', () => {
const promise = Blacklist.create(data).then(
(result) => {
id = result._id;
return Blacklist.create(data);
});
return expect(promise).to.be.rejected;
});
it('Should fail due to lost connection', () => {
return test_db.close(true).then(() => {
connected = false;
return expect(Blacklist.create(data)).to.be.rejected;
});
});
afterEach(() => connected ?
Blacklist.delete(id) : connect().then(() => Blacklist.delete(id)));
});
});
I call real functions in tests, which is seemingly awkward and time-consuming in runtime to avoid side-effects in my humble opinion. But currently I have not come up with any other ideas apart from altering a test database. Is there a way to use sinon? I have read several blogs about sinon, spy, stub, and mock, but struggle to understand and distinguish them. How could I apply them on these tests?
What you have currently written are integration tests which test the interaction between your node server and mongo db database. Although these tests are more time consuming then mocked unit tests they actually provide far more value. Running queries against a stable MongoDB instance is ensures that your queries are running as planned and that your application is responding properly to the results see: How to unit test a method which connects to mongo, without actually connecting to mongo?.
If you wish to test the javascript functions that manipulate the data as opposed to interaction between the server and db. I would suggest that you refactor out this code from the mongodb query logic and unit test it. Alternatively as you are using a class you should be able to overwrite the _db property with a mock db library. Which would just be an object with methods that mimic that of the mongo library you are currently using. Or you could use sinon to stub out those methods and replace them with methods that return a know result see http://sinonjs.org/releases/v1.17.7/stubs/.
Try something like this:
var ensureIndex = { ensureIndex: sinon.stub() }
sinon.stub(db, 'collection').returns(ensureIndex)
var blackList;
describe('Model: Blacklist', () => {
beforeEach(() => {
var blackList = new BlacklistModel(db, id, logger);
})
it('test' => {
blackList.create(data).then(() => {
// some test here
db.collection.calledWithMatch('some match')
})
})
})
One simple way to do this is stub and return custom object.
By using this way, you could also verify the functionality by examining the args and return value of stub function.
Here is my example
// your class
class TestCase{
constructor(db){
this.db = db;
}
method1(args1){
this.db.insertOne(args1)
}
method2(args2){
this.db.f(args2)
}
}
// test file
const sinon = require('sinon');
const sandbox = sinon.createSandbox();
const stubInsertOne = sandbox.stub();
const stubFindOne = sandbox.stub();
const stubMongo = {
insertOne: stubInsertOne,
findOne: stubFindOne
}
describe("TestCase", ()=>{
beforeEach(()=>{
// reset the sandbox or the stub result is polluted
sandbox.reset();
})
it("method1 test", ()=> {
stubInsertOne.resolves("what ever you want to mock return value");
const testCase = new TestCase(stubMongo);
testCase.method1();
})
.....
})
The downside is that you have to manually stub every function call used in mongodb.

Categories