Search code examples
node.jsexpressjestjsmockingsupertest

How to mock imported express middleware with jest and using supertest?


I have tests using supertest to connect to a server and I am trying to mock an authentication and an authorisation middlewares with Jest. I have found couple other questions which answers I have tried to combine this question is about mocking an imported function and then there are couple questions about mocking an express middleware: this, this one and also this

I have tried to combine the answers in those questions and came up with this

// server.ts
import express from 'express';
import { authentication } from 'auth-lib'

const server = express();

server.use(express.json());
server.use(authentication);
server.use('/someRoute', someRouter);

export { server };
// someRoute.test.ts
import { Server } from 'http';
import request from 'supertest';
import { server } from '../path/to/server'


describe('Some tests', () => {
  let app: Server;

  beforeAll(done => {
    app = server.listen(3210, done)
  });


  afterAll(done => {
    app.close(() => done())
  });

  it('POST someRoute', async () => {
    /**
     * Mocking the authentication middleware
     */
    jest.mock('auth-lib', () => ({
      __esModule: true,
      authentication: (req, res, next): void => {
        res.locals.authentication = { auth: true, authToken: 'asdasasd' };
        next();
      }
    }));
    /**
     * But it doesn't work
     */

    const res = await request(app)
      .post('/someRoute')
      .send(testData);

    expect(res.status).toEqual(200);
  });
});

This doesn't seem to work. I have had some logging in the mocked middleware and the execution doesn't seem ever even go in there. I have also had some other variants of the mock, but these don't work either

jest.mock('auth-lib', () => ({
  __esModule: true,
  authentication: jest.fn((req, res, next): void => {
    res.locals.authentication = { auth: true, authToken: 'asdasasd' };
    next();
  })
}));
jest.mock('auth-lib', () => ({
  __esModule: true,
  authentication: jest.fn().mockImplementation((req, res, next): void => {
    res.locals.authentication = { auth: true, authToken: 'asdasasd' };
    next();
  })
}));

What is the correct way to mock the middlewares so that the res object is also extended and passed forward in the middleware chain?


Solution

  • jest.mock() only works in module scope. And you don't need pass the factory function, let it

    Mocks a module with an auto-mocked version when it is being required. factory and options are optional

    Then, you can provide difference mock implementation in each test case.

    auth-lib.ts:

    export const authentication = (req, res, next) => {
      console.log('real implementation');
    };
    

    server.ts:

    import express from 'express';
    import { authentication } from './auth-lib';
    
    const server = express();
    
    server.use(express.json());
    server.use(authentication);
    server.post('/someRoute', (req, res) => {
      console.log('res.locals.authentication: ', res.locals.authentication);
      res.sendStatus(200);
    });
    
    export { server };
    

    someRoute.test.ts:

    import request from 'supertest';
    import { server } from './server';
    import { authentication } from './auth-lib';
    
    jest.mock('./auth-lib');
    
    const authenticationmock = jest.mocked(authentication);
    
    describe('Some tests', () => {
      it('POST someRoute', async () => {
        authenticationmock.mockImplementation((req, res, next) => {
          res.locals.authentication = { auth: true, authToken: 'asdasasd' };
          next();
        });
        const res = await request(server).post('/someRoute');
        expect(res.status).toEqual(200);
      });
    });
    

    Test result:

      console.log
        res.locals.authentication:  { auth: true, authToken: 'asdasasd' }
    
          at log (stackoverflow/78639299/server.ts:9:11)
    
     PASS  stackoverflow/78639299/someRoute.test.ts (8.62 s)
      Some tests
        √ POST someRoute (38 ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        19.408 s
    Ran all test suites related to changed files.
    

    package versions:

    "express": "^4.19.2",
    "jest": "^29.7.0",
    "supertest": "^7.0.0",