How to mock a module's function only for a specific test with Jest

Viewed 2411

I need to mock the function of a dependency but only for a specific test case. In my other tests, I want to leave the module as is. I am also mocking other modules. Those need to stay mocked.

I tried multiple approaches as many variants of this question have been asked, but due to my need to use the original module when not mocked, none of the other solutions seem to work...

Here's an example of what I would need

// app.ts
import * as moduleA from 'moduleA';

// ...

app.post('/signup', async (req, res) => {
   // ...
   const hashed = await moduleA.hash(req.body.password);
   // ...
});

export default app;


// app.test.ts

// some mocks
jest.mock('./database', () => ({ ... }));
// ...

import * as request from 'supertest';
import app from './app';

// ...

describe('A', () => {
    test('Those should work', async () => {
        const response = await request(app).post('/signup').send({ password: 'pw' });
        expect(response.status).toBe(200);
        // ...
    });
    // many other tests
    
    test('I need to force hash to crash here', async () => {
        // mock moduleA.hash only for this test
        const response = request(app).post('/signup').send({ password: 'pw' });
        expect(response.status).toBe(500);
    });

    test('moduleA.hash should be back to its default function', async () => {
        // request(app) and moduleA.hash will work properly, not mocked
        // ./database stays mocked
    });
});
1 Answers

You can use jest.spyOn() to mock the hash method of moduleA, and use .mockRestore() to restore the hash method to original implementation.

Here is the solution:

app.ts:

import express from 'express';
import bodyParser from 'body-parser';

import * as moduleA from './moduleA';
const app = express();

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.post('/signup', async (req, res) => {
  try {
    const hashed = await moduleA.hash(req.body.password);
    console.log(hashed);
    res.sendStatus(200);
  } catch (error) {
    res.sendStatus(500);
  }
});

export default app;

moduleA.ts:

export const hash = async (data: string) => {
  return 'real hashed value';
};

app.spec.ts:

import app from './app';
import request from 'supertest';
import * as moduleA from './moduleA';

describe('app', () => {
  test('Those should work', async () => {
    const response = await request(app)
      .post('/signup')
      .send({ password: 'pw' });
    expect(response.status).toBe(200);
  });

  test('I need to force hash to crash here', async () => {
    const mockedError = new Error('hash error');
    const hashSpy = jest.spyOn(moduleA, 'hash').mockRejectedValueOnce(mockedError);
    const response = await request(app)
      .post('/signup')
      .send({ password: 'pw' });
    expect(response.status).toBe(500);
    expect(moduleA.hash).toBeCalledWith('pw');
    hashSpy.mockRestore();
  });

  test('moduleA.hash should be back to its default function', async () => {
    const logSpy = jest.spyOn(console, 'log');
    const response = await request(app)
      .post('/signup')
      .send({ password: 'pw' });
    expect(response.status).toBe(200);
    expect(logSpy).toBeCalledWith('real hashed value');
  });
});

Unit test result with 100% coverage:

PASS  src/stackoverflow/47540126/app.spec.ts
  app
    ✓ Those should work (72ms)
    ✓ I need to force hash to crash here (10ms)
    ✓ moduleA.hash should be back to its default function (14ms)

  console.log src/stackoverflow/47540126/app.ts:3569
    real hashed value

  console.log node_modules/jest-mock/build/index.js:860
    real hashed value

------------|----------|----------|----------|----------|-------------------|
File        |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files   |      100 |      100 |      100 |      100 |                   |
 app.ts     |      100 |      100 |      100 |      100 |                   |
 moduleA.ts |      100 |      100 |      100 |      100 |                   |
------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        4.156s, estimated 5s

Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/47540126

Related