5

I am new to express and very new to unit testing. Consider the following code:

var express = require('express');
var router = express.Router();

var bookingsController = require ("../controllers/bookings");

router
   .route('/')
   .get(bookingsController.bookings_get)
   .post(bookingsController.bookings_post)


router
   .route('/:id')
   .get(bookingsController.bookings_get_id)
   .put(bookingsController.bookings_put_id)
   .delete(bookingsController.bookings_delete_id)

module.exports = router;

What is the right/recommended way of writing unit tests for this? I would like to be able to test, for example, that router.route('/:id') does not accept POST calls.

I know I can do this with supertest, but I believe that would be considered an integration test as supertest would start the app the run the test.

I've read and tried multiple node.js/express.js testing tutorials but couldn't find an answer to this. Any pointers to existing tutorials most welcome. Direct answers here, even more :)

Przemek
  • 203
  • 3
  • 10
  • Well, what you're asking for IS kind of an integration test. You want to ensure that there is no route anywhere in the server that handles a post for `/:id`. There is no code you can test to see if it does something so the only way to test for the absence of something is to run the server and make sure you get a 404 back when you fire that route with a POST. For just that router, you could insert just that router in a dummy server, run that server and test the dummy server. – jfriend00 Apr 30 '20 at 17:58
  • 1
    You could probably find some undocumented API in the router for handling an incoming request and try to simulate an incoming request that way without a real server, but I'm not sure that's more valuable than testing the actual server. Or you could try to mock an express server and install the router in it and fire a request at the mock server through some mock API. But, you'll have to build from scratch in your mock server how an express server integrates with your router which will again involve calling private APIs in the router. – jfriend00 Apr 30 '20 at 18:01
  • In an older [post](https://stackoverflow.com/questions/35234718/how-to-unit-test-express-router-routes) @Peter Haight discusses how to do this with Jasmine. Would love to see if/how it is possible with sinon and/or proxyquire. – Przemek Apr 30 '20 at 18:25
  • None of those answers do what you're asking about. You're asking to test for the absence of something in the final router which is definitely a different problem. You'll have to inspect internal data structures in the router or mock what an express server sends to the router and look at the response you get. – jfriend00 Apr 30 '20 at 19:47

1 Answers1

5

Here is the unit test solution using sinon.js to stub express.Router function and make assertions.

router.js:

var express = require('express');
var router = express.Router();

var bookingsController = require('./controllers/bookings');

router
  .route('/')
  .get(bookingsController.bookings_get)
  .post(bookingsController.bookings_post);

router
  .route('/:id')
  .get(bookingsController.bookings_get_id)
  .put(bookingsController.bookings_put_id)
  .delete(bookingsController.bookings_delete_id);

module.exports = router;

./controller/bookings.js:

module.exports = {
  bookings_get() {},
  bookings_post() {},
  bookings_get_id() {},
  bookings_put_id() {},
  bookings_delete_id() {},
};

router.test.js:

var sinon = require('sinon');
var express = require('express');

describe('61529619', () => {
  it('should pass', () => {
    const routerStub = {
      route: sinon.stub().returnsThis(),
      post: sinon.stub().returnsThis(),
      get: sinon.stub().returnsThis(),
      put: sinon.stub().returnsThis(),
      delete: sinon.stub().returnsThis(),
    };
    sinon.stub(express, 'Router').callsFake(() => routerStub);
    require('./router');
    sinon.assert.calledWith(routerStub.route, '/');
    sinon.assert.calledWith(routerStub.route, '/:id');
    sinon.assert.calledWith(routerStub.get, sinon.match.func);
    sinon.assert.calledWith(routerStub.post, sinon.match.func);
    sinon.assert.calledWith(routerStub.put, sinon.match.func);
    sinon.assert.calledWith(routerStub.delete, sinon.match.func);
  });
});

unit test results with 100% coverage:

  61529619
    ✓ should pass (869ms)


  1 passing (881ms)

----------------------|---------|----------|---------|---------|-------------------
File                  | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------------------|---------|----------|---------|---------|-------------------
All files             |     100 |      100 |       0 |     100 |                   
 61529619             |     100 |      100 |     100 |     100 |                   
  router.js           |     100 |      100 |     100 |     100 |                   
 61529619/controllers |     100 |      100 |       0 |     100 |                   
  bookings.js         |     100 |      100 |       0 |     100 |                   
----------------------|---------|----------|---------|---------|------------------
Lin Du
  • 88,126
  • 95
  • 281
  • 483