How do I test custom Koa middleware for error handling? - javascript

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

Related

Jest mocks - how to restore original function between tests

I have my function:
report.js:
const ctrl = {};
const _ = require('lodash');
const boom = require("boom");
const slackNotifications = require('./../../slack/controllers/notifications');
const reportModel = require("./../models/report");
import validationSchema from "./../_validation/report";
ctrl.addReport = async (req, res) => {
const { body } = req;
try {
const validatedData = await validationSchema.validate(body);
const report = await reportModel(req.dbConnection).addReport(validatedData);
if (report.reported) {
await slackNotifications.notify('Notify me!');
res.status(200).send({ reported: true });
} else {
throw boom.serverUnavailable("Can't offer report");
}
} catch (err) {
throw boom.badRequest(err);
}
};
module.exports = ctrl;
The validation schema is schema created by using yup.js.
Here are the tests
The problem is throw Error when validation failed test. I've got
TypeError: Cannot read property 'output' of undefined
from 109 line
const { output, output: { payload } } = err;.
But my expected value is error threw by validationSchema.validate and caught in 23rd line of my function.
When I run only this test everything is ok. I've got the correct error with status code and message.
How can I restore the original function validationSchema.validate from this test (84th line)?
I've tried to restore by:
jest.mock('./../_validation/report', () => ({
// validate: jest.fn().mockReset(),
validate: jest.fn().mockClear(),
}));
But I'm not sure that is the correct way.
You can use requireActual in your test to execute the original behaviour for just one test.
This way :
jest.mock('./../_validation/report', () => ({
validate: jest.requireActual('./../_validation/report').validate,
}));
This way, the real validate will be called
Of course, at the next test (or in a beforeEach), you can re-mock your function.

How can I test a catch block using jest nodejs?

How can i test the catch block on a es6 Class
const fs = require('fs');
class Service {
constructor(accessToken) {
this.accessToken = accessToken;
}
async getData() { // eslint-disable-line class-methods-use-this
try {
const data = fs.readFileSync(`${__dirname}/models/mockData.json`, { encoding: 'utf8' });
const returnData = JSON.parse(data);
return returnData;
} catch (err) {
return err;
}
}
}
module.exports = Service;
using jest how can i write the test case to cover the catch block also.
You can mock the method readFileSync from fs to force it to return undefined. JSON.parse(undefined) will throw an error, thus you can check the catch side of the code.
fs.readFileSync = jest.fn()
fs.readFileSync.mockReturnValue(undefined);
First of all, in the catch side you should throw the error. Just returning it is not a good practise when managing errors, from my point of view. But there is people doing it.
const fs = require('fs');
class Service {
constructor(accessToken) {
this.accessToken = accessToken;
}
async getData() { // eslint-disable-line class-methods-use-this
try {
const data = fs.readFileSync(`${__dirname}/models/mockData.json`, { encoding: 'utf8' });
const returnData = JSON.parse(data);
return returnData;
} catch (err) {
throw err;
}
}
}
Having this code, you can actually test your catch block code in two different ways with Jest:
beforeEach(() => {
fs.readFileSync = jest.fn();
});
afterEach(() => {
fs.readFileSync.mockClear();
});
test('Async expect test', () => {
fs.readFileSync.mockReturnValue(undefined);
const result = service.getData();
expect(result).rejects.toThrow();
});
test('Async / await test', async() => {
fs.readFileSync.mockReturnValue(undefined);
try {
await service.getData();
} catch (err) {
expect(err.name).toEqual('TypeError');
expect(err.message).toEqual(`Cannot read property 'charCodeAt' of undefined`);
}
});
Both of them imply to mock the readFileSync method from fs module as I suggested before. You can even mock the whole fs module with Jest. Or you could just mock the JSON.parse. There are plenty of options to be able to test the catch block.
Jest has its own method for testing exception, you can use toThrow. It looks something like this
test('throws on octopus', () => {
expect(() => {
drinkFlavor('octopus');
}).toThrow(); // Test the exception here
});
Note
Since your function is asynchronous, try to explicitly define your error, then use await to resolve/reject it, After that you can check for the actual rejection
test('throws on octopus', () => {
await expect(user.getUserName(3)).rejects.toEqual({
error: 'User with 3 not found.',
});
});

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.

How to stub a function in a module?

I build express app, there is a route A use many middlewares:
// fblogin.js
const saveUser = require('./middlewares').saveUser;
const issueJWT = require('./middlewares').issueJWT;
const exchangeLongTimeToken = (a) => { //return promise to call API };
const retrieveUserInfo = (b) => { //return promise to call API };
const service = {
exchangeLongTimeToken,
retrieveUserInfo,
};
const asyncAll = (req, res) => {
// use Promise.all() to get service.exchangeLongTimeToken
// and service.retrieveUserInfo
};
router.post('/', [asyncAll, saveUser, issueJWT], (req, res) => {
//some logic;
});
module.exports = { router, service };
And this is my middlewares.js:
const saveUser = (req, res, next) => { //save user };
const issueJWT = (req, res, next) => { //issue jwt };
module.exports = { saveUser, issueJWT };
It works well. But got problem when I tried to write the test.
This is my test, I use mocha, chai, supertest and sinon:
const sinon = require('sinon');
const middlewares = require('../../../../src/routes/api/auth/shared/middlewares');
const testData = require('../../testdata/data.json');
let app = require('../../../../src/config/expressapp').setupApp();
const request = require('supertest');
let service = require('../../../../src/routes/api/auth/facebook/fblogin').service;
describe('I want to test', () => {
context('Let me test', function () {
const testReq = {name: 'verySad'};
beforeEach(() => {
sinon.stub(middlewares, 'saveUser').callsFake((req, res, next)=>{
console.log(req);
});
sinon.stub(service, 'exchangeLongTimeToken').callsFake((url) => {
return Promise.resolve(testData.fbLongTimeToken);
});
sinon.stub(service, 'retrieveUserInfo').callsFake((url) => {
return Promise.resolve(testData.fbUserInfo);
});
});
it('Should return 400 when bad signedRequest', () => {
return request(app).post(facebookAPI).send(testReq).then((response) => {
response.status.should.be.equal(400);
});
});
});
What is the problem
You could see that there are 3 stubs, 1 for middlewares.saveUser and 2 for services.XXXX which is in the same file of the route.
The problem is that, the 2 stubs works while the 1 for middlewares.saveUser not work, always trigger the original one.
I think it maybe that when I call the setupApp(), the express will load all the routers it needs, so mock it afterwards won't have a effect, but it
is strange that route.service could be mocked...
How to get the stub work?
The only way to get it work, is to put the stub at the top of test file, just after that middleware require.
I tried:
1. Use 3rd party modules like proxyquire, rewire
2. Use node's own delete require.cache[middlewares] and 'app' and re-require them.
3. Many other tricks.
4. Use jest's mock, but still only works if I put it at the top of the file.
What is the way of solving this problem without putting the stub at the top of the test file? Thanks!
The solution in the question is a bit restricted, since the mock has polluted the whole test suites.
I end up by doing this, The logic is simple, we still need to mock the saveUser first, but then we require all the other variables into the test function rather than require them at the top of the file, more flexible this time. And I add a checkIfTheStubWorks method to check if the stub works, to make sure the whole test works.
const middlewares = require('../../../../src/routes/api/auth/shared/middlewares');
const removeEmptyProperty = require('../../../../src/utils/utils').removeEmptyProperty;
let app;
let service;
let request;
/*
* The reason we need this is:
* If the mock not works,
* the whole test is meaningless
*/
const checkIfTheStubWorks = () => {
expect(spy1).toHaveBeenCalled();
expect(spy2).toHaveBeenCalled();
expect(spy3).toHaveBeenCalled();
};
const loadAllModules = () => {
service = require('../../../../src/routes/api/auth/facebook/fblogin').service;
app = require('../../../../src/config/expressapp').setupApp();
request = require('supertest')(app);
};
describe('Mock response from facebook', () => {
let spy1 = {};
let spy2 = {};
let spy3 = {};
const testReq = testData.fbShortToken;
beforeAll(() => {
spy1 = jest.spyOn(middlewares, 'saveUser').mockImplementation((req, res, next) => {
userToSaveOrUpdate = removeEmptyProperty(res.locals.user);
next();
});
// It must be load here, in this order,
// otherwise, the above mock won't work!
loadAllModules();
spy2 = jest.spyOn(service, 'exchangeLongTimeToken').mockImplementation((url) => {
// mock it
});
spy3 = jest.spyOn(service, 'retrieveUserInfo').mockImplementation((url) => {
// mock it
});
});
afterAll(() => {
spy1.mockRestore();
spy2.mockRestore();
spy3.mockRestore();
});
test('Return a JWT should have same length as facebook one', async () => {
const response = await request.post(facebookAPI).send(testReq);
// assert this, assert that
checkIfTheStubWorks();
});
});

Categories