Search code examples
node.jsexpressunit-testingjestjssupertest

Jest mock third-party default export per individual test


I am trying to test an express controller using jest and supertest. I have a middleware that handles file processing with multer. I can mock multer globally but I have been unable to mock it per it/test cases.

// saveImage.controller.ts
jest.mock('multer', () => {
  const m = () => ({
    single: () => (req: Request, res: Response, next: NextFunction) => {
      req.body = { type: 'screenshots' };
      req.file = mockFile;
      return next();
    },
  });
  m.memoryStorage = () => jest.fn();
  return m;
});

describe('POST /api/v1/garment/image/upload', () => {
  it('should return 201', async () => {
    await request(app)
      .post('/api/v1/garment/image/upload')
      .send({ file: 'myImage' }) // this is overwritten by the mock 
      .expect(201); // passes
  });

  it('should return 400', async () => {
    await request(app)
      .post('/api/v1/garment/image/upload')
      .send() // this is overwritten by the mock
      .expect(400); // fails
  });
});

The first test is passing but I am unable to alter the mock to make the second test pass. How can change the mock in my second it case?


Solution

  • I will use jest.doMock(moduleName, factory, options) to mock multer. Note that the multer.single() method will be executed when the module is imported, so we mock it before the module is imported.

    In order to clear the cache when the module is imported (can cache the module scope variable, the function of the function, etc.), you need to use jest.resetModules() before running each test case.

    app.ts:

    import express from 'express';
    import multer from 'multer';
    import path from 'path';
    
    const upload = multer({ dest: path.join(__dirname, 'uploads/') });
    const app = express();
    
    app.post('/api/v1/garment/image/upload', upload.single('file'), (req, res) => {
      console.log(req.file, req.body);
      if (req.file) {
        res.sendStatus(201);
      } else {
        res.sendStatus(400);
      }
    });
    
    export { app };
    

    app.test.ts:

    import request from 'supertest';
    import multer from 'multer';
    import { NextFunction, Request, Response } from 'express';
    
    describe('69997349', () => {
      let app: typeof import('./app').app;
      beforeEach(async () => {
        jest.resetModules();
      });
      it('should return 201', async () => {
        const mockFile = { fieldname: 'myImage' } as Express.Multer.File;
        jest.doMock('multer', () => {
          const m = () => ({
            single: () => (req: Request, res: Response, next: NextFunction) => {
              req.body = { type: 'screenshots' };
              req.file = mockFile;
              return next();
            },
          });
          return m;
        });
        app = (await import('./app')).app;
        await request(app).post('/api/v1/garment/image/upload').send({ file: 'myImage' }).expect(201);
      });
    
      it('should return 400', async () => {
        jest.doMock('multer', () => {
          const m = () => ({
            single: () => (req: Request, res: Response, next: NextFunction) => {
              return next();
            },
          });
          return m;
        });
        app = (await import('./app')).app;
        await request(app).post('/api/v1/garment/image/upload').expect(400);
      });
    });
    

    test result:

     PASS  examples/69997349/app.test.ts (8.245 s)
      69997349
        ✓ should return 201 (297 ms)
        ✓ should return 400 (23 ms)
    
      console.log
        { fieldname: 'myImage' } { type: 'screenshots' }
    
          at examples/69997349/app.ts:10:11
    
      console.log
        undefined undefined
    
          at examples/69997349/app.ts:10:11
    
    Test Suites: 1 passed, 1 total
    Tests:       2 passed, 2 total
    Snapshots:   0 total
    Time:        9.428 s, estimated 10 s