jest icon indicating copy to clipboard operation
jest copied to clipboard

jest.clearAllMocks(); does not remove mock implementation within `afterEach`

Open cphoover opened this issue 6 years ago • 98 comments

🐛 Bug Report

jest.clearAllMocks(); does not remove mock implementation within afterEach

To Reproduce

I have a file called src/layouts/index.js

// ./src/layouts/index.js
const importAll = (r) =>
    r.keys().reduce(
        (acc, key) => ({
            ...acc,
            [key.replace(/^\.\/(.*).json$/, '$1')]: r(key)
        }),
        {}
    );

module.exports = importAll(require.context('./', true, /\.json$/));

It utilizes webpack require.context so I am trying to mock with jest.mock.

I have another file... say file util.js

//./src/util.js
import layouts from '../layouts';
export const getLayout(name) {
  return layouts[name];
}

in my test I'm trying to clear the mocks after each test

//./src/util.test.js
describe('my test suite', () => {
  afterEach(() => {
     jest.clearAllMocks();
  })
  test('test number one', () => {
      jest.mock('./layouts', () => ({
          layout1 : { a : 1 },
          layout2 : { b: 2 },
     }));
     assert.equals(getLayout('layout1').a, 1);
     assert.equals(getLayout('layout2').b, 2);
  });
  test('test number two', () => {
     assert.equals(getLayout('layout1').a, 1);
     assert.equals(getLayout('layout2').b, 2);
  });
});

Expected behavior

I would expect for the first test to pass and the second test to fail... because the mock should have been cleared.

Link to repl or repo (highly encouraged)

https://repl.it/@CharlieHoover/SorrowfulBackSandboxes

cphoover avatar Oct 10 '18 21:10 cphoover

As I understand the parallel execution model of jest the tests inside each suite are run sequentially so you should be able to mock per individual test.

cphoover avatar Oct 10 '18 21:10 cphoover

FYI The mocking documentation and API is extremely unclear, and overly complicated IMHO.

cphoover avatar Oct 10 '18 21:10 cphoover

jest.clearAllMocks does not remove mock implementations by design - try jest.resetAllMocks

Here are the relevant docs:

rickhanlonii avatar Oct 10 '18 22:10 rickhanlonii

Still does not work with resetAllMocks:

Example included:

https://repl.it/@CharlieHoover/SorrowfulBackSandboxes-2

Aside from that that is extremely ambiguous. Why would a function called clearAllMocks not clear the mocks... Name the function resetMockState or something more descriptive. clearAllMocks implies the mocks are being cleared.

cphoover avatar Oct 11 '18 13:10 cphoover

@rickhanlonii my issue is not yet answered. I want to remove the mocks.

cphoover avatar Oct 11 '18 13:10 cphoover

Ah, yeah, looks like resetAllMocks does not reset mock module factories just the implementations set by mockImplementation. If you want to post what you want to do to stackoverflow I can help you do what you want there but it doesn't look like there's a bug here

Why would a function called clearAllMocks not clear the mocks

I think the confusion is that the "mock" in "clearAllMocks" does not refer to the mock implementations, it refers to the Jest mock objects. So this function means "clear out all jest mock objects" which is to say call .mockClear on all mock objects (i.e. clear the calls)

rickhanlonii avatar Oct 11 '18 14:10 rickhanlonii

@rickhanlonii

omg so #1 it seems like "clear" and "reset" are being used opposite to what their logical meaning is.

Also, it's very clear what he's trying to do; remove the mock implementation, and you're saying there's no way to do that orrr.....?????

ccemeraldeyes avatar Oct 16 '19 21:10 ccemeraldeyes

This should be reopened

ccemeraldeyes avatar Oct 16 '19 21:10 ccemeraldeyes

+1

MikeSpitz avatar Oct 28 '19 16:10 MikeSpitz

+1

montezume avatar Oct 31 '19 10:10 montezume

+1

AlvinLaiPro avatar Nov 05 '19 01:11 AlvinLaiPro

I have a similar issue, when I mock an implementation in previous it case, the next it case will be affected.

AlvinLaiPro avatar Nov 05 '19 01:11 AlvinLaiPro

+1

alexwaibel avatar Nov 10 '19 22:11 alexwaibel

also struggling with this!

Lagily avatar Nov 15 '19 11:11 Lagily

+1 please update the docs to explain how to REMOVE a mock/spy

d4nc3r avatar Nov 27 '19 15:11 d4nc3r

+1 please update the docs to explain how to REMOVE a mock/spy

Isn't this what mockRestore is for? https://jestjs.io/docs/en/mock-function-api#mockfnmockrestore

JimmayVV avatar Dec 13 '19 17:12 JimmayVV

I think the default config should include:

{
  restoreMocks: true,
  clearMocks: true,
  resetMocks: true
} 

It is shocking that the default behaviour is to vomit state between tests. Can you please just keep my tests isolated by default? Then the [hopeful minority] who want to spread state across multiple tests can do so by opt-in.

damien-roche avatar Dec 16 '19 09:12 damien-roche

` describe('test', () => { beforeEach(() => { const WelcomeService = require('./../SOME_MODULE') WelcomeServiceSpyOfMessage = jest.spyOn( WelcomeService, 'message', // some function I mocked ) const IsUserAuthentic = require('./../SOME_MODULE') IsUserAuthenticSpyOnIsUserAuthentic = jest.spyOn( IsUserAuthentic, 'isUserAuthentic' // some function I mocked ) app = require('../src/server') // my Express server })

afterEach(() => {
  jest.restoreAllMocks()
})

it('1. Mock implementation', async () => {
  const mockedMessage = faker.lorem.sentence()
  WelcomeServiceSpyOfMessage.mockImplementation(() => mockedMessage)
  IsUserAuthenticSpyOnIsUserAuthentic.mockImplementation(() => {
    console.log('>>> MOCKED MW 1')
    return true
  })
  const result = await request(app)
    .get('/api')
  expect(result.statusCode).toBe(200)
  expect(result.body).toHaveProperty('message', mockedMessage)
})

it('2. After restored implementation', async () => {
  IsUserAuthenticSpyOnIsUserAuthentic.mockImplementation(() => {
    console.log('>>> MOCKED MW 2')
    return true
  })
  const result = await request(app)
    .get('/api')
  expect(result.statusCode).toBe(200)
  expect(result.body).toHaveProperty('message', 'hello world')
})

}) ` Output: console.log test/routes.test.js:36 >>> MOCKED MW 1

console.log test/routes.test.js:36 >>> MOCKED MW 1

Same mocked version of function is called for both the tests. Although I have restored all mocks in afterEach call, still same mock is getting called. Please tell me where I missed.

Thanks

ankur20us avatar Dec 17 '19 18:12 ankur20us

+1 🥂

JoaoFGuiomar avatar Jan 03 '20 16:01 JoaoFGuiomar

+1

TorranceYang avatar Jan 10 '20 19:01 TorranceYang

+1 It seems to me that clearing the mocks after each test should be the default behavior.

vinyoliver avatar Jan 20 '20 03:01 vinyoliver

+1 It seems to me that clearing the mocks after each test should be the default behavior.

+1

robbporto avatar Jan 22 '20 20:01 robbporto

functions mocked with .spyOn() can be restored: jest.spyOn(object, method).mockImplementation(mockFunction).

I agree that mocks should be cleared automatically between tests, though.

joshssarna avatar Jan 24 '20 17:01 joshssarna

+1

karl-frascari avatar Jan 24 '20 22:01 karl-frascari

+1

saitho avatar Jan 27 '20 13:01 saitho

Is there a fix for this issue? +1

cedricsarigumba avatar Feb 06 '20 09:02 cedricsarigumba

Facing the same issue!

el-lsan avatar Feb 13 '20 16:02 el-lsan

I have same issue but I not found solution from jest official documentation. So I make a not graceful solution below. Then use it in everywhere that need to remove mock.

// jestUtil.js
const mockArr = [];
export function doAMock(moduleName, cb = null) {
  if (!mockArr.includes(moduleName)) {
    mockArr.push(moduleName);
  }
  if (cb) {
    jest.doMock(moduleName, cb);
  } else {
    jest.doMock(moduleName);

  }
}

export function removeAllMock() {
  mockArr.forEach(m => {
    jest.dontMock(m);
  });
  mockArr.length = 0;
}


// usage
// other.test.js
import * as jsUtil from './jestUtil.js';
jsUtil.doAMock('module');
jsUtil.removeAllMock();

akiratfli avatar Feb 19 '20 05:02 akiratfli

IMO this is a super confusing API naming and deserves to be renamed.

gaearon avatar Feb 24 '20 19:02 gaearon

IMO this is a super confusing API naming and deserves to be renamed.

yeah, totally agree - but I'm still wondering if this is currently possible - naming aside. If yes, how?

pkyeck avatar Mar 25 '20 10:03 pkyeck