javascriptnode.jsexpressjestjssupertest

How to test Express router catch branch in this scenario using Jest?


I have a file with two routes.

routes/index.js

const express = require('express')
const router = express.Router()

router.get('', (req, res, next) => {
  try {
    res.status(200).render('../views/')
  } catch (error) {
    next(error)
  }
})

router.get('*', (req, res, next) => {
  try {
    res.status(404).render('../views/not-found')
  } catch (error) {
    next(error)
  }  
})

module.exports = router

I want to test the catch branches. One of the obvious problems is that I can't fake what the callback of router.get does, because then my test would be pointless, I would modify the very thing that I want to test.

I can send a Supertest request to these specific routes, but then I have no control over what happens. I want to avoid creating a hardcoded route, only so I can check whether the middleware is called by the next function. If I understand correctly, if I would have additional things happening in the try block (like a database query) than I could mock that to throw an error.

But I do not have any additional things happening there. Here is what I can not wrap my head around: if I mock what the server does, then I don't test what I already have but something else, so there is no point in doing that. Maybe I misunderstand how things work, but as far as I can see the only option would be to somehow mock what res.status or res.render does (stub them so they throw an error), so somehow mock what Supertest does, but I have no idea how to do that.

Any help would be greatly appreciated!


Solution

  • unit test solution:

    route.js:

    const express = require('express');
    const router = express.Router();
    
    router.get('', (req, res, next) => {
      try {
        res.status(200).render('../views/');
      } catch (error) {
        next(error);
      }
    });
    
    router.get('*', (req, res, next) => {
      try {
        res.status(404).render('../views/not-found');
      } catch (error) {
        next(error);
      }
    });
    
    module.exports = router;
    

    route.test.js:

    describe('64051580', () => {
      afterEach(() => {
        jest.resetModules();
        jest.restoreAllMocks();
      });
      it('should render views', () => {
        const express = require('express');
        const mRouter = { get: jest.fn() };
        jest.spyOn(express, 'Router').mockImplementationOnce(() => mRouter);
        const mReq = {};
        const mRes = { status: jest.fn().mockReturnThis(), render: jest.fn() };
        const mNext = jest.fn();
        mRouter.get.mockImplementation((path, callback) => {
          if (path === '') {
            callback(mReq, mRes, mNext);
          }
        });
        require('./route');
        expect(mRes.status).toBeCalledWith(200);
        expect(mRes.render).toBeCalledWith('../views/');
      });
    
      it('should handle error', () => {
        const express = require('express');
        const mRouter = { get: jest.fn() };
        jest.spyOn(express, 'Router').mockImplementationOnce(() => mRouter);
        const mReq = {};
        const mErr = new Error('parse');
        const mRes = {
          status: jest.fn().mockReturnThis(),
          render: jest.fn().mockImplementationOnce(() => {
            throw mErr;
          }),
        };
        const mNext = jest.fn();
        mRouter.get.mockImplementation((path, callback) => {
          if (path === '') {
            callback(mReq, mRes, mNext);
          }
        });
        require('./route');
        expect(mNext).toBeCalledWith(mErr);
      });
    
      it('should render 404 not found view', () => {
        const express = require('express');
        const mRouter = { get: jest.fn() };
        jest.spyOn(express, 'Router').mockImplementationOnce(() => mRouter);
        const mReq = {};
        const mRes = { status: jest.fn().mockReturnThis(), render: jest.fn() };
        const mNext = jest.fn();
        mRouter.get.mockImplementation((path, callback) => {
          if (path === '*') {
            callback(mReq, mRes, mNext);
          }
        });
        require('./route');
        expect(mRes.status).toBeCalledWith(404);
        expect(mRes.render).toBeCalledWith('../views/not-found');
      });
    });
    

    unit test result with coverage report:

     PASS  src/stackoverflow/64051580/route.test.js
      64051580
        ✓ should render views (656ms)
        ✓ should handle error (17ms)
        ✓ should render 404 not found view (16ms)
    
    ----------|----------|----------|----------|----------|-------------------|
    File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    ----------|----------|----------|----------|----------|-------------------|
    All files |    90.91 |      100 |      100 |    90.91 |                   |
     route.js |    90.91 |      100 |      100 |    90.91 |                16 |
    ----------|----------|----------|----------|----------|-------------------|
    Test Suites: 1 passed, 1 total
    Tests:       3 passed, 3 total
    Snapshots:   0 total
    Time:        4.28s, estimated 10s