node.js - Apply sinon on mongodb unit tests - javascript

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.

Related

Is it possible to mock API calls with Jest in NodeJS without jest.mock('module')?

My NodeJS application has to do some API requests, so I'm mocking their return as my tests are just for my application's business logic. However, there's two things that I quite didn't understand.
I'm using jest's mockImplementation method to change the return of my service, but I can't make it work without calling jest.mock with the service beforehand.
Also, if I try to set automock: true in my jest.config.js, it returns me an error:|
TypeError: Cannot set property 'gracefulify' of undefined
Here's my test.js code in where I'm testing a function that calls automation.js, which has my application logic and make the calls for my services:
const automation = require('../automations/fake.automation');
// MOCKS
const mockedBlingProduct = require('../mocks/bling-product.mocks.json');
const mockedShopifyCreatedProduct = require('../mocks/shopify-created-product.mocks.json');
// SERVICES
const BlingProductService = require('../services/bling-product.service');
const ShopifyProductService = require('../services/shopify-product.service');
jest.mock('../services/bling-product.service');
jest.mock('../services/shopify-product.service');
describe('Automation test', () => {
beforeEach(() => {
const blingMockedReturn = jest.fn(() => {
return mockedBlingProduct;
});
const shopifyMockedReturn = jest.fn(() => {
return mockedShopifyCreatedProduct;
});
BlingProductService.mockImplementation(() => {
return {
list: blingMockedReturn
};
});
ShopifyProductService.mockImplementation(() => {
return {
create: shopifyMockedReturn
};
});
});
it('should return status SUCCESS', async () => {
const result = await
.run();
expect(result).toEqual({ status: 'SUCCESS' });
});
});
And here's the code of one of my services, keep in mind that the logic behind the API calls is abstracted from the service. In the mockImplementation I'm trying to overwrite the list and create functions inside them:
class BlingPriceService {
async list(query = {}) {
const httpMethod = 'GET';
const resource = 'produtos/page={pageNumber}/json';
const options = {
queryString: query,
urlParams: {
pageNumber: 1,
}
};
return blingComponent.request(httpMethod, resource, options);
}
}
module.exports = BlingPriceService;
const automation = require('../automations/fake.automation');
// MOCKS
const mockedBlingProduct = require('../mocks/bling-product.mocks.json');
const mockedShopifyCreatedProduct = require('../mocks/shopify-created-product.mocks.json');
// SERVICES
const BlingProductService = require('../services/bling-product.service');
const ShopifyProductService = require('../services/shopify-product.service');
describe('Automation test', () => {
beforeAll(() => {
jest.spyOn(BlingProductService.prototype, 'list').mockImplementation(() => Promise.resolve(mockedBlingProduct));
jest.spyOn(ShopifyProductService.prototype, 'list').mockImplementation(() => Promise.resolve(mockedShopifyCreatedProduct));
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should return status SUCCESS', async () => {
const result = await automation.run();
expect(result).toEqual({ status: 'SUCCESS' });
});
});

testing variable within the function in class using mocha

I need to test value of the url variable in the pullPackage() function in the TASK class.
class TASK {
constructor(taskData, done) {
//some code
}
// Generic Setup
pullPackage() {
return new Promise((resolve, reject) => {
fs.emptydir(this.taskDir, (err) => {
if (err) return reject(err);
const git = require('simple-git')(this.taskDir);
let url = '';
console.log(process.env.NODE_ENV);
if (process.env.NODE_ENV === 'test') {
// url = 'ssh://testuser#127.0.0.1:4000/testuser/test-repo-1.git'; // make this match the below format
url = '/git/testuser/test-repo-1';
} else {
const gitAddress = new URL(config.config.GIT_ADDRESS);
url = `${gitAddress.protocol}//runner:${this.taskData.gitJWT}#${gitAddress.hostname}:${gitAddress.port}${this.taskData.repo}.git`;
}
// console.log(url);
// const url = `${gitAddress.protocol}//runner:${this.taskData.gitJWT}#${gitAddress.hostname}:${gitAddress.port}${this.taskData.repo}.git`;
this.logger.log('Cloning from', url);
return git.clone(url, 'repo', (cloneErr) => {
if (cloneErr) return reject(cloneErr);
// console.log(url);
// console.log(resolve);
return resolve(true);
});
});
});
}
}
I'm using Mocha and Chai to do this. I have two test for this function, to check the variable and the promise. The second test runs as expected, but the first always return fails with AssertionError: expected undefined not to be undefined. I think the issue is how I'm accessing the variable during testing. Currently I'm doing it like this: expect(result.url).to.not.be.undefined; Am I going about this correctly?
describe('Test MenloLab Runner - Task Class', () => {
describe('Pull Package', () => {
it('Check URL constant.', () => task.pullPackage().then((result) => {
expect(result.url).to.not.be.undefined; // adjust the access method
}));
it('It should pull package from GIT.', () => task.pullPackage().then((result) => {
expect(result).to.be.true;
}));
});
});
The workaround to check the URL could be done by spying on git.clone method. To do that, we need to use Sinon
I haven't tested with your code but I give you a clue on the solution below:
const sinon = require('sinon');
const git = require('simple-git');
describe('Test MenloLab Runner - Task Class', () => {
describe('Pull Package', () => {
it('Check URL constant.', () => {
return task.pullPackage().then((result) => {
sinon.spy(git, 'clone'); // spying on git.clone method
expect(result.url).to.not.be.undefined; // adjust the access method
const expectedUrl = 'my-expected-not-undefined-url';
sinon.assertCalledWith(git.clone, expectedUrl); // we check whether git.clone is called with not undefined URL
});
});
it('It should pull package from GIT.', () => task.pullPackage().then((result) => {
expect(result).to.be.true;
}));
});
});
Hope it helps

jest: how to mock a dependency that listens to events?

I came across a very complicated situation. I'll try to keep it as concise as possible.
So I have a code like this in myModule.js:
const lib = require('#third-party/lib');
const myFunction = () => {
const client = lib.createClient('foo');
return new Promise((resolve, reject) => {
client.on('error', (err) => reject(err));
client.on('success', () => {
client.as(param1).post(param2, param3, (err, data) => {
if (err) reject(err);
// Some important logical processing of data
resolve(data);
});
});
});
}
module.exports = { myFunction };
There are a few things I am able to mock, like: createClient.
What I am not able to mock is the event part I don't even know how to do this. And the .as().post() part.
Here's how my jest test looks like:
const myModule = require('./myModule');
const mockData = require('./mockData');
describe('myFunction', () => {
it('Should resolve promise when lib calls success event', async () => {
try {
const myData = await myModule.myFunction();
expect(myData).toMatchObject(mockData.resolvedData);
} catch (err) {
expect(err).toBeNull();
}
})
});
Any help, much Appreciated.
I tried to find similar questions but at this point, my mind has just stopped working...
Please let me know if you need any more details.
Here’s what you need to do:
// EventEmitter is here to rescue you
const events = require("events");
// Mock the third party library
const lib = require("#third-party/lib");
lib.createClient.mockImplementationOnce(params => {
const self = new events.EventEmitter();
self.as = jest.fn().mockImplementation(() => {
// Since we're calling post on the same object.
return self;
});
self.post = jest.fn().mockImplementation((arg1, _cb) => {
// Can have a conditional check for arg 1 if so desird
_cb(null, { data : "foo" });
});
// Finally call the required event with delay.
// Don't know if the delay is necessary or not.
setInterval(() => {
self.emit("success");
}, 10);
return self;
}).mockImplementationOnce(params => {
const self = new events.EventEmitter();
// Can also simulate event based error like so:
setInterval(() => {
self.emit("error", {message: "something went wrong."});
}, 10);
return self;
}).mockImplementationOnce(params => {
const self = new events.EventEmitter();
self.as = jest.fn().mockImplementation(() => {
return self;
});
self.post = jest.fn().mockImplementation((arg1, _cb) => {
// for negative callback in post I did:
_cb({mesage: "Something went wrong"}, null);
});
setInterval(() => {
self.emit("success");
}, 10);
return self;
});
This is only the mock object that you need to put in your test.js file.
Not sure if this code will work as is, although won’t require a lot of debugging.
If you just want to positive scenario, remove the second mockImplementationOnce and replace the first mockImplementationOnce with just mockImplementation.

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();
});
}

How do I test custom Koa middleware for error handling?

As part of a migration of an older app from ExpressJs to Koa JS (v1). I've written a piece of middleware to handle any errors that occur. It looks something like this:
module.errors = function * (next) {
try {
yield next;
} catch (err) {
switch(err && err.message) {
case: 'Bad Request':
this.status = 400;
this.body = {message: 'Bad Request'};
brea;
default:
this.status = 500;
this.body = {message: 'An error has occurred'};
}
this.app.emit('error', err, this);
}
}
It gets included in my application like this:
const app = require('koa')();
const router = require('koa-router');
const { errors } = require('./middleware/errors');
app.use(errors)
.use(router.routes());
app.get('/some-request', function *(next){
// request that could error
});
app.listen();
This all works fine, but I'd like to test the middleware with my unit tests, and perhaps because I'm still fairly new to both Koa and Generator functions, I'm struggling to figure out how to do this.
I know that if I import the error handling middleware, I need to pass it a function that will throw an error, but how do I execute the function passed? Does it need to be closure of some description? How do I assert/expect on the values set for the status code and the like?
const { expect } = require('chai');
const { errors } = require('../middleware/errors');
describe('errors middleware', () => {
it('returns a 500 on a generic error', () => {
let thrower = function(){ throw new Error() }
let errorHandler = errors(thrower());
// mass of confusion
expect(errorHandler.next()).to.throw(Error);
});
});
Koa middlewares are generators (return/yield multiple times) and don't behave like functions, so it feels weird to write unit tests for them. Personally, I suffice with end-to-end test cases.
However, the following might work in your case.
const { expect } = require('chai');
const { errors } = require('../middleware/errors');
describe('errors middleware', () => {
it('returns a 500 on a generic error', () => {
let ctx = { body: {}, status: 404 };
let errorMidIterator = errors().call(ctx, 'NEXT_MID');
// test that it correctly yields to next middleware
expect(errorMidIterator.next().value).should.equal('NEXT_MID');
// simualte an error and test if it correctly sets the body
expect(errorMidIterator.throw(new Error()).done).to.equal(true);
expect(ctx.status).should.equal(500);
});
});
As a side note, I think it is better to export middleware factories from your files rather than plain middleware generator functions. The former gives you more control (i.e. you can possibly inject some of the dependencies, in this case the thrower() function, through the Factory function arguments). My middleware files look like these.
module.exports = function MyMiddleware(options) {
return function *MyMiddleware(next) {
// options.config.foo
// options.httpclient.get(..)
};
}
Lastly koa wraps the generator functions with co, which changes the semantics so unit tests are not that useful (subjective)
you can use this library https://www.npmjs.com/package/co which used by koa.js 1.x to wrap your generator functions and mock the context object.
const co = require('co');
const Emitter = require('events');
const { expect } = require('chai');
const { errors } = require('../middleware/errors');
const wrapped = co.wrap(errors);
const mockApp = new Emitter();
describe('errors middleware', () => {
it('returns a 500 on a generic error', (done) => {
const ERROR_MSG = 'middleware error';
const ctx = {app: mockApp};
const next = function* () {
throw new Error(ERROR_MSG);
}
wrapped.call(ctx, next)
.then(() => {
try {
expect(ctx.status).to.equal(500);
expect(ctx.body.message).to.equal(ERROR_MSG);
done();
} catch (err) {
done(err);
}
})
.catch(err => done(err))
});
});
This is how I solved this problem with Jest, I just created a custom res object and passed it to error handler:
const error = require('../../../middleware/error');
describe('error middleware', () => {
it(' return 500 if there is unhandled error', async () => {
const res = {
status: (c) => {this.c = c; return {send: (s) => {this.s = s; return this}}} ,
c: 200,
s: 'OK',
};
const req = {};
const next = jest.fn();
const err = () => {
throw new Error()
};
const errorHandler = error(err, req, res, next);
expect(errorHandler).toMatchObject({c: 500, s: 'Something failed'});
});
});

Categories