jest: [Bug]: resetAllMocks restoring mocks instead of resetting them

Version

29.4.0

Steps to reproduce

Simple test to reproduce:

test('it should reset properly', () => {
  const obj = {
    foo: () => 42,
  };

  const fooSpy = jest.spyOn(obj, 'foo');

  console.log(obj.foo());
  expect(obj.foo).toHaveBeenCalled();

  jest.resetAllMocks();

  console.log(obj.foo());
  expect(obj.foo).toHaveBeenCalled();
});

Expected behavior

The test should pass

Actual behavior

The test fails with Matcher error: received value must be a mock or spy function.

Additional context

It seems that resetAllMocks is acting like mockRestore instead of mockReset.

If I replace jest.resetAllMocks() with fooSpy.mockReset(), the test passes as expected, whereas if I replace it with fooSpy.mockRestore() it fails with that error again.

Environment

System:
    OS: Linux 5.15 KDE neon 5.26 5.26
    CPU: (16) x64 AMD Ryzen 7 PRO 6850U with Radeon Graphics
  Binaries:
    Node: 18.12.0 - ~/.nvm/versions/node/v18.12.0/bin/node
    npm: 8.19.2 - ~/.nvm/versions/node/v18.12.0/bin/npm
  npmPackages:
    jest: ^29.4.0 => 29.4.0

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 18
  • Comments: 16 (6 by maintainers)

Most upvoted comments

Should be fixed in #13866

No, I don’t know enough about Jest internals to suggest a fix.

~Besides, I’d expect that a certain multi-billion-dollar corporation would be maintaining this. Don’t get me wrong, I’m all for contributing to open source but if the project is backed by a huge corporation, I think they can spare the time to fix it.~ (see https://jestjs.io/blog/2022/05/11/jest-joins-openjs, thanks @mrazauskas for pointing it out)

I have a different problem, but I think the cause is the same change in mockReset. Someone called spyOn on a mock function and it caused a maximum call stack error This is an example that reproduces the error:

describe('test console.log', () => {
  console.log = jest.fn();
  const log = jest.spyOn(console, 'log');

  afterEach(() => {
    log.mockReset();
  });

  test('console.log a', () => {
    console.log('test a');

    expect(log).toBeCalledWith('test a');
  });

  test('console.log b', () => {
    /**
     * An error is thrown on the next line:
     * RangeError: Maximum call stack size exceeded
     * at WeakMap.get (<anonymous>)
     * at ModuleMocker._ensureMockState (node_modules/jest-mock/build/index.js:275:33)
     */
    console.log('test b');

    expect(log).toBeCalledWith('test b');
  });
});

When I remove the line this._mockConfigRegistry.set(f, originalMockImpl); it works fine.

Seems related: I’m also getting random RangeError: Maximum Call Stack Size Exceeded that are pointing to the jest-mock package, reverting pack to 29.3.1 fixes the issue.

@feliperli Could you take a look, please? Seems like your PR (#13692) introduced this issue. Or is that expected behaviour?

Oh no 😦 I’m looking into it.

29.4.3 fixes the original issue for me, along with the “Maximum Call Stack Size Exceeded” error. Thanks all!

Looks like the bug wasn’t fixed in mockReset

I’ve created a new issue at https://github.com/facebook/jest/issues/13916

I also see this!!!

TIL, thanks for pointing that out.

I did end up doing some digging, and while I’m not any closer to finding the root cause, I did notice this:

  resetAllMocks(): void {
    this._spyState.forEach(reset => reset());
    this._mockConfigRegistry = new WeakMap();
    this._mockState = new WeakMap();
  }

  restoreAllMocks(): void {
    this._spyState.forEach(restore => restore());
    this._spyState = new Set();
  }

It seems that both of those methods end up calling all the _spyState functions and on line 1401 just above, you see this._spyState.add(restore); so it seems that resetAllMocks is calling the restore function (but confusingly naming it reset in the forEach).

I’m not sure if this is intentional, or if it’s not, what a fix would look like, but hopefully this info at least helps.