Search code examples
javascriptexpressjestjsintegration-testingsupertest

jest + supertest: how to reset the mocked dependency


I am not able to reset jest mocks of a dependency after it's being used once by supertest. I appreciate any help or hint.

The following is my api test with supertest:

import request from 'supertest';
import router from '../index';
const app = require('express')();
app.use(router);

jest.mock('config', () => ({}));

jest.mock('express-request-proxy', () => data => (req, res, next) =>
  res.json(data),
);

beforeEach(() => {
  jest.resetAllMocks();
});

describe('GET /', () => {
  it('should get all stuff', () =>
    request(app)
      .get('')
      .expect(200)
      .expect('Content-Type', /json/)
      .then(response => {
        expect(response.body).toMatchSnapshot();             // all good here 
      }));

  it('should get all stuff when FOO=bar', async () => {
    jest.mock('config', () => ({
      FOO: 'bar',
      get: key => key,
    }));

    // >> HERE <<
    // config.FOO is still undefined!
    // reseting mocks did not work ...
    await request(app)
      .get('')
      .expect(200)
      .expect('Content-Type', /json/)
      .then(response => {
        expect(response.body.query).toHaveProperty('baz');   // test fails ...
      });
  });
});

express.js api:

const router = require('express').Router({ mergeParams: true });
import config from 'config';
import proxy from 'express-request-proxy';

router.get('', (...args) => {
  let query = {};
  if (config.FOO === 'bar') {
    query.baz = true;
  }
  return proxy({
    url: '/stuff',
    query,
  })(...args);
});

Solution

  • You can't use jest.mock(moduleName, factory, options) in a function scope, it should be used in the module scope. You should use jest.doMock(moduleName, factory, options) if you want to arrange the mocks in function scope of test cases.

    We also need to use jest.resetModules() before executing each test case to

    reset the module registry - the cache of all required modules.

    It means your ./config module registry will be reset so that it will return different mocked values for each test case when you require it after jest.doMock('./config', () => {...}) statement.

    { virtual: true } option means I don't install the express-request-proxy package so it doesn't exist in my npm_modules directory. If you had already installed it, you can remove this option.

    Here is the unit test solution:

    index.js:

    const router = require('express').Router({ mergeParams: true });
    
    import config from './config';
    import proxy from 'express-request-proxy';
    
    router.get('', (...args) => {
      console.log(config);
      let query = {};
      if (config.FOO === 'bar') {
        query.baz = true;
      }
      return proxy({ url: '/stuff', query })(...args);
    });
    
    export default router;
    

    config.js:

    export default {
      FOO: '',
    };
    

    index.test.js:

    import request from 'supertest';
    
    jest.mock('express-request-proxy', () => (data) => (req, res, next) => res.json(data), { virtual: true });
    
    beforeEach(() => {
      jest.resetAllMocks();
      jest.resetModules();
    });
    
    describe('GET /', () => {
      it('should get all stuff', () => {
        jest.doMock('./config', () => ({}));
        const router = require('./index').default;
        const app = require('express')();
        app.use(router);
    
        return request(app)
          .get('')
          .expect(200)
          .expect('Content-Type', /json/)
          .then((response) => {
            expect(response.body).toMatchSnapshot();
          });
      });
    
      it('should get all stuff when FOO=bar', async () => {
        jest.doMock('./config', () => ({
          default: {
            FOO: 'bar',
            get: (key) => key,
          },
          __esModule: true,
        }));
        const router = require('./index').default;
        const app = require('express')();
        app.use(router);
    
        await request(app)
          .get('')
          .expect(200)
          .expect('Content-Type', /json/)
          .then((response) => {
            expect(response.body.query).toHaveProperty('baz');
          });
      });
    });
    

    unit test results with 100% coverage report:

     PASS  stackoverflow/61828748/index.test.js (11.966s)
      GET /
        ✓ should get all stuff (8710ms)
        ✓ should get all stuff when FOO=bar (24ms)
    
      console.log
        {}
    
          at stackoverflow/61828748/index.js:7:11
    
      console.log
        { FOO: 'bar', get: [Function: get] }
    
          at stackoverflow/61828748/index.js:7:11
    
    ----------|---------|----------|---------|---------|-------------------
    File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ----------|---------|----------|---------|---------|-------------------
    All files |     100 |      100 |     100 |     100 |                   
     index.js |     100 |      100 |     100 |     100 |                   
    ----------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       2 passed, 2 total
    Snapshots:   1 passed, 1 total
    Time:        13.268s
    

    index.test.js.snap:

    // Jest Snapshot v1
    
    exports[`GET / should get all stuff 1`] = `
    Object {
      "query": Object {},
      "url": "/stuff",
    }
    `;
    

    source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61828748