4

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);
  });
});
purpletonic
  • 1,858
  • 2
  • 18
  • 29

3 Answers3

0

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)

zeronone
  • 2,912
  • 25
  • 28
0

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))

  });
});
wenshin
  • 167
  • 1
  • 6
0

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'});
    });
});
frzsombor
  • 2,274
  • 1
  • 22
  • 40
Slayver Sayto
  • 23
  • 1
  • 4