node.jstypescriptexpressjestjssupertest

How do I mock an express middleware class method with jest and supertest?


I am having difficulty getting the expected result while mocking a method within a class using jest and supertest and I was wondering if anyone knew a solution.

I am trying to bypass the verifyAuthenticated method in the original class with the mocked version because it calls out to an external service.

This is a simple example

import express, { Application, Request, Response } from "express";
import authMiddleware from "./AuthMiddleware";

const app: Application = express();

app.disable("x-powered-by");

const rootHandler = (request: Request, response: Response) => {
  response.json({ status: "online" });
};

app.get("/", [authMiddleware.verifyAuthenticated, rootHandler]);

export default app;
import { Request, Response, NextFunction } from "express";

class AuthMiddleware {
  constructor() {
    console.log("AuthMiddleware instance created");
  }

  verifyAuthenticated = (request: Request, response: Response, next: NextFunction) => {
    // In the real world this would call out to an external service and verify user credentials.
    console.log("This is the real deal");
    next();
  };
}

export default new AuthMiddleware();
import request from "supertest";
import app from "../src/app";
import authMiddleware from "../src/AuthMiddleware";
import { NextFunction, Request, Response } from "express";

describe("GET /", () => {
  test("Should return online.", async () => {
    jest
      .spyOn(authMiddleware, "verifyAuthenticated")
      .mockImplementation((req: Request, res: Response, next: NextFunction) => {
        console.log("This is the mock deal");
        next();
      });

    const agent = request(app);
    const response = await agent.get("/").send();

    expect(response.statusCode).toBe(200);
    expect(response.headers["content-type"]).toContain("application/json");
    expect(response.body.status).toBe("online");
  });
});

The test results are as follows:

> node-express-typescript@1.0.0 test
> jest

  console.log
    AuthMiddleware instance created

      at new AuthMiddleware (src/AuthMiddleware.ts:5:13)

  console.log
    This is the real deal

      at AuthMiddleware.verifyAuthenticated (src/AuthMiddleware.ts:9:13)

 PASS  tests/app.test.ts
  GET /
    ✓ Should return online. (39 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.316 s, estimated 3 s
Ran all test suites.

However I was expecting This is the mock deal. I have tried multiple different variants on importing and rewriting the class but none of them work and they all give the same result.


Solution

  • Mock the authMiddleware.verifyAuthenticated() method before importing the app. We can use dynamic import() method to import the app.

    app.test.ts:

    import request from 'supertest';
    // import app from './app';
    import authMiddleware from './AuthMiddleware';
    import { NextFunction, Request, Response } from 'express';
    
    describe('GET /', () => {
        test('Should return online.', async () => {
            jest
                .spyOn(authMiddleware, 'verifyAuthenticated')
                .mockImplementation((req: Request, res: Response, next: NextFunction) => {
                    console.log('This is the mock deal');
                    next();
                });
    
            const app = (await import('./app')).default;
            const agent = request(app);
            const response = await agent.get('/').send();
    
            expect(response.statusCode).toBe(200);
            expect(response.headers['content-type']).toContain('application/json');
            expect(response.body.status).toBe('online');
        });
    });
    

    Test result:

      console.log
        AuthMiddleware instance created
    
          at new AuthMiddleware (stackoverflow/77281063/AuthMiddleware.ts:5:11)
    
      console.log
        This is the mock deal
    
          at stackoverflow/77281063/app.test.ts:11:13
    
     PASS  stackoverflow/77281063/app.test.ts
      GET /
        ✓ Should return online. (91 ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        0.747 s, estimated 7 s