From 42cfacba0f44a2e2bfdd43270852875b97ac25f7 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Tue, 7 Feb 2023 15:55:53 +0200 Subject: [PATCH 1/5] fix(jest-mock): clear mock when `jest.restoreAllMocks()` is called --- .../jest-mock/src/__tests__/index.test.ts | 128 +++++++++++++++--- packages/jest-mock/src/index.ts | 1 + 2 files changed, 111 insertions(+), 18 deletions(-) diff --git a/packages/jest-mock/src/__tests__/index.test.ts b/packages/jest-mock/src/__tests__/index.test.ts index 56c7c4adee54..16f868dc682c 100644 --- a/packages/jest-mock/src/__tests__/index.test.ts +++ b/packages/jest-mock/src/__tests__/index.test.ts @@ -1312,6 +1312,36 @@ describe('moduleMocker', () => { ); }); + it('supports restoring a spy', () => { + let methodOneCalls = 0; + const obj = { + methodOne() { + methodOneCalls++; + }, + }; + + const spy1 = moduleMocker.spyOn(obj, 'methodOne'); + + obj.methodOne(); + + // The spy and the original function got called. + expect(methodOneCalls).toBe(1); + expect(spy1.mock.calls).toHaveLength(1); + + expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(true); + + spy1.mockRestore(); + + // After restoring the spy, the method is not mock function. + expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(false); + + obj.methodOne(); + + // After restoring the spy only the real method bumps its call count, not the spy. + expect(methodOneCalls).toBe(2); + expect(spy1.mock.calls).toHaveLength(0); + }); + it('supports restoring all spies', () => { let methodOneCalls = 0; let methodTwoCalls = 0; @@ -1327,25 +1357,32 @@ describe('moduleMocker', () => { const spy1 = moduleMocker.spyOn(obj, 'methodOne'); const spy2 = moduleMocker.spyOn(obj, 'methodTwo'); - // First, we call with the spies: both spies and both original functions - // should be called. obj.methodOne(); obj.methodTwo(); + + // Both spies and both original functions got called. expect(methodOneCalls).toBe(1); expect(methodTwoCalls).toBe(1); expect(spy1.mock.calls).toHaveLength(1); expect(spy2.mock.calls).toHaveLength(1); + expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(true); + expect(moduleMocker.isMockFunction(obj.methodTwo)).toBe(true); + moduleMocker.restoreAllMocks(); - // Then, after resetting all mocks, we call methods again. Only the real - // methods should bump their count, not the spies. + // After restoring all mocks, the methods are not mock functions. + expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(false); + expect(moduleMocker.isMockFunction(obj.methodTwo)).toBe(false); + obj.methodOne(); obj.methodTwo(); + + // After restoring all mocks only the real methods bump their count, not the spies. expect(methodOneCalls).toBe(2); expect(methodTwoCalls).toBe(2); - expect(spy1.mock.calls).toHaveLength(1); - expect(spy2.mock.calls).toHaveLength(1); + expect(spy1.mock.calls).toHaveLength(0); + expect(spy2.mock.calls).toHaveLength(0); }); it('should work with getters', () => { @@ -1482,6 +1519,33 @@ describe('moduleMocker', () => { ); }); + it('supports restoring a spy', () => { + let methodOneCalls = 0; + const obj = { + get methodOne() { + return function () { + methodOneCalls++; + }; + }, + }; + + const spy1 = moduleMocker.spyOn(obj, 'methodOne', 'get'); + + obj.methodOne(); + + // The spy and the original function are called. + expect(methodOneCalls).toBe(1); + expect(spy1.mock.calls).toHaveLength(1); + + spy1.mockRestore(); + + obj.methodOne(); + + // After restoring the spy only the real method bumps its call count, not the spy. + expect(methodOneCalls).toBe(2); + expect(spy1.mock.calls).toHaveLength(0); + }); + it('supports restoring all spies', () => { let methodOneCalls = 0; let methodTwoCalls = 0; @@ -1501,10 +1565,10 @@ describe('moduleMocker', () => { const spy1 = moduleMocker.spyOn(obj, 'methodOne', 'get'); const spy2 = moduleMocker.spyOn(obj, 'methodTwo', 'get'); - // First, we call with the spies: both spies and both original functions - // should be called. obj.methodOne(); obj.methodTwo(); + + // Both spies and both original functions got called. expect(methodOneCalls).toBe(1); expect(methodTwoCalls).toBe(1); expect(spy1.mock.calls).toHaveLength(1); @@ -1512,14 +1576,14 @@ describe('moduleMocker', () => { moduleMocker.restoreAllMocks(); - // Then, after resetting all mocks, we call methods again. Only the real - // methods should bump their count, not the spies. obj.methodOne(); obj.methodTwo(); + + // After restoring all mocks only the real methods bump their count, not the spies. expect(methodOneCalls).toBe(2); expect(methodTwoCalls).toBe(2); - expect(spy1.mock.calls).toHaveLength(1); - expect(spy2.mock.calls).toHaveLength(1); + expect(spy1.mock.calls).toHaveLength(0); + expect(spy2.mock.calls).toHaveLength(0); }); it('should work with getters on the prototype chain', () => { @@ -1587,6 +1651,34 @@ describe('moduleMocker', () => { expect(obj.property).toBe(true); }); + it('supports restoring a spy on the prototype chain', () => { + let methodOneCalls = 0; + const prototype = { + get methodOne() { + return function () { + methodOneCalls++; + }; + }, + }; + const obj = Object.create(prototype, {}); + + const spy1 = moduleMocker.spyOn(obj, 'methodOne', 'get'); + + obj.methodOne(); + + // The spy and the original function are called. + expect(methodOneCalls).toBe(1); + expect(spy1.mock.calls).toHaveLength(1); + + spy1.mockRestore(); + + obj.methodOne(); + + // After restoring the spy only the real method bumps its call count, not the spy. + expect(methodOneCalls).toBe(2); + expect(spy1.mock.calls).toHaveLength(0); + }); + it('supports restoring all spies on the prototype chain', () => { let methodOneCalls = 0; let methodTwoCalls = 0; @@ -1607,10 +1699,10 @@ describe('moduleMocker', () => { const spy1 = moduleMocker.spyOn(obj, 'methodOne', 'get'); const spy2 = moduleMocker.spyOn(obj, 'methodTwo', 'get'); - // First, we call with the spies: both spies and both original functions - // should be called. obj.methodOne(); obj.methodTwo(); + + // Both spies and both original functions got called. expect(methodOneCalls).toBe(1); expect(methodTwoCalls).toBe(1); expect(spy1.mock.calls).toHaveLength(1); @@ -1618,14 +1710,14 @@ describe('moduleMocker', () => { moduleMocker.restoreAllMocks(); - // Then, after resetting all mocks, we call methods again. Only the real - // methods should bump their count, not the spies. obj.methodOne(); obj.methodTwo(); + + // After restoring all mocks only the real methods bump their count, not the spies. expect(methodOneCalls).toBe(2); expect(methodTwoCalls).toBe(2); - expect(spy1.mock.calls).toHaveLength(1); - expect(spy2.mock.calls).toHaveLength(1); + expect(spy1.mock.calls).toHaveLength(0); + expect(spy2.mock.calls).toHaveLength(0); }); }); diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index e5b84cc59f23..158ad37a51bb 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -1414,6 +1414,7 @@ export class ModuleMocker { } restoreAllMocks(): void { + this._mockState = new WeakMap(); this._spyState.forEach(restore => restore()); this._spyState = new Set(); } From 21352d1c195ba244430a638f16c7b73e6507b504 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Tue, 7 Feb 2023 16:02:45 +0200 Subject: [PATCH 2/5] add changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 724c300efe7c..8c409ba51bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Fixes +- `[jest-mock]` Clear mock state when `jest.restoreAllMocks()` is called ([#13867](https://github.com/facebook/jest/pull/13867)) + ### Chore & Maintenance ### Performance From a1046c944d521b6918d879b49a6745ae06ba91a7 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Tue, 7 Feb 2023 18:39:13 +0200 Subject: [PATCH 3/5] fix typo --- packages/jest-mock/src/__tests__/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-mock/src/__tests__/index.test.ts b/packages/jest-mock/src/__tests__/index.test.ts index 16f868dc682c..ecceafad3e5f 100644 --- a/packages/jest-mock/src/__tests__/index.test.ts +++ b/packages/jest-mock/src/__tests__/index.test.ts @@ -1756,7 +1756,7 @@ describe('moduleMocker', () => { expect(obj.property).toBe(1); }); - it('should allow mocking with value of different value', () => { + it('should allow mocking with value of different type', () => { const obj = { property: 1, }; From ddec0bc8c571137adb379fabbd62e6bb632c58d0 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Tue, 7 Feb 2023 18:44:54 +0200 Subject: [PATCH 4/5] extend tests --- .../jest-mock/src/__tests__/index.test.ts | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/jest-mock/src/__tests__/index.test.ts b/packages/jest-mock/src/__tests__/index.test.ts index ecceafad3e5f..e354a13aa026 100644 --- a/packages/jest-mock/src/__tests__/index.test.ts +++ b/packages/jest-mock/src/__tests__/index.test.ts @@ -1314,32 +1314,45 @@ describe('moduleMocker', () => { it('supports restoring a spy', () => { let methodOneCalls = 0; + let methodTwoCalls = 0; const obj = { methodOne() { methodOneCalls++; }, + methodTwo() { + methodTwoCalls++; + }, }; const spy1 = moduleMocker.spyOn(obj, 'methodOne'); + const spy2 = moduleMocker.spyOn(obj, 'methodTwo'); obj.methodOne(); + obj.methodTwo(); - // The spy and the original function got called. + // Both spies and both original functions got called. expect(methodOneCalls).toBe(1); + expect(methodTwoCalls).toBe(1); expect(spy1.mock.calls).toHaveLength(1); + expect(spy2.mock.calls).toHaveLength(1); expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(true); + expect(moduleMocker.isMockFunction(obj.methodTwo)).toBe(true); spy1.mockRestore(); - // After restoring the spy, the method is not mock function. + // After restoring `spy1`, the method is not mock function. expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(false); + expect(moduleMocker.isMockFunction(obj.methodTwo)).toBe(true); obj.methodOne(); + obj.methodTwo(); - // After restoring the spy only the real method bumps its call count, not the spy. + // After restoring `spy1` only the real method bumps its call count, not the spy. expect(methodOneCalls).toBe(2); + expect(methodTwoCalls).toBe(2); expect(spy1.mock.calls).toHaveLength(0); + expect(spy2.mock.calls).toHaveLength(2); }); it('supports restoring all spies', () => { @@ -1521,29 +1534,42 @@ describe('moduleMocker', () => { it('supports restoring a spy', () => { let methodOneCalls = 0; + let methodTwoCalls = 0; const obj = { get methodOne() { return function () { methodOneCalls++; }; }, + get methodTwo() { + return function () { + methodTwoCalls++; + }; + }, }; const spy1 = moduleMocker.spyOn(obj, 'methodOne', 'get'); + const spy2 = moduleMocker.spyOn(obj, 'methodTwo', 'get'); obj.methodOne(); + obj.methodTwo(); - // The spy and the original function are called. + // Both spies and both original functions got called. expect(methodOneCalls).toBe(1); + expect(methodTwoCalls).toBe(1); expect(spy1.mock.calls).toHaveLength(1); + expect(spy2.mock.calls).toHaveLength(1); spy1.mockRestore(); obj.methodOne(); + obj.methodTwo(); - // After restoring the spy only the real method bumps its call count, not the spy. + // After restoring `spy1` only the real method bumps its call count, not the spy. expect(methodOneCalls).toBe(2); + expect(methodTwoCalls).toBe(2); expect(spy1.mock.calls).toHaveLength(0); + expect(spy2.mock.calls).toHaveLength(2); }); it('supports restoring all spies', () => { @@ -1653,30 +1679,43 @@ describe('moduleMocker', () => { it('supports restoring a spy on the prototype chain', () => { let methodOneCalls = 0; + let methodTwoCalls = 0; const prototype = { get methodOne() { return function () { methodOneCalls++; }; }, + get methodTwo() { + return function () { + methodTwoCalls++; + }; + }, }; const obj = Object.create(prototype, {}); const spy1 = moduleMocker.spyOn(obj, 'methodOne', 'get'); + const spy2 = moduleMocker.spyOn(obj, 'methodTwo', 'get'); obj.methodOne(); + obj.methodTwo(); - // The spy and the original function are called. + // Both spies and both original functions got called. expect(methodOneCalls).toBe(1); + expect(methodTwoCalls).toBe(1); expect(spy1.mock.calls).toHaveLength(1); + expect(spy2.mock.calls).toHaveLength(1); spy1.mockRestore(); obj.methodOne(); + obj.methodTwo(); - // After restoring the spy only the real method bumps its call count, not the spy. + // After restoring `spy1` only the real method bumps its call count, not the spy. expect(methodOneCalls).toBe(2); + expect(methodTwoCalls).toBe(2); expect(spy1.mock.calls).toHaveLength(0); + expect(spy2.mock.calls).toHaveLength(2); }); it('supports restoring all spies on the prototype chain', () => { From 66c5354eceefaa1a29fc2965de701d2371f2a8aa Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Wed, 8 Feb 2023 08:01:12 +0200 Subject: [PATCH 5/5] Revert "extend tests" This reverts commit ddec0bc8c571137adb379fabbd62e6bb632c58d0. --- .../jest-mock/src/__tests__/index.test.ts | 53 +++---------------- 1 file changed, 7 insertions(+), 46 deletions(-) diff --git a/packages/jest-mock/src/__tests__/index.test.ts b/packages/jest-mock/src/__tests__/index.test.ts index e354a13aa026..ecceafad3e5f 100644 --- a/packages/jest-mock/src/__tests__/index.test.ts +++ b/packages/jest-mock/src/__tests__/index.test.ts @@ -1314,45 +1314,32 @@ describe('moduleMocker', () => { it('supports restoring a spy', () => { let methodOneCalls = 0; - let methodTwoCalls = 0; const obj = { methodOne() { methodOneCalls++; }, - methodTwo() { - methodTwoCalls++; - }, }; const spy1 = moduleMocker.spyOn(obj, 'methodOne'); - const spy2 = moduleMocker.spyOn(obj, 'methodTwo'); obj.methodOne(); - obj.methodTwo(); - // Both spies and both original functions got called. + // The spy and the original function got called. expect(methodOneCalls).toBe(1); - expect(methodTwoCalls).toBe(1); expect(spy1.mock.calls).toHaveLength(1); - expect(spy2.mock.calls).toHaveLength(1); expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(true); - expect(moduleMocker.isMockFunction(obj.methodTwo)).toBe(true); spy1.mockRestore(); - // After restoring `spy1`, the method is not mock function. + // After restoring the spy, the method is not mock function. expect(moduleMocker.isMockFunction(obj.methodOne)).toBe(false); - expect(moduleMocker.isMockFunction(obj.methodTwo)).toBe(true); obj.methodOne(); - obj.methodTwo(); - // After restoring `spy1` only the real method bumps its call count, not the spy. + // After restoring the spy only the real method bumps its call count, not the spy. expect(methodOneCalls).toBe(2); - expect(methodTwoCalls).toBe(2); expect(spy1.mock.calls).toHaveLength(0); - expect(spy2.mock.calls).toHaveLength(2); }); it('supports restoring all spies', () => { @@ -1534,42 +1521,29 @@ describe('moduleMocker', () => { it('supports restoring a spy', () => { let methodOneCalls = 0; - let methodTwoCalls = 0; const obj = { get methodOne() { return function () { methodOneCalls++; }; }, - get methodTwo() { - return function () { - methodTwoCalls++; - }; - }, }; const spy1 = moduleMocker.spyOn(obj, 'methodOne', 'get'); - const spy2 = moduleMocker.spyOn(obj, 'methodTwo', 'get'); obj.methodOne(); - obj.methodTwo(); - // Both spies and both original functions got called. + // The spy and the original function are called. expect(methodOneCalls).toBe(1); - expect(methodTwoCalls).toBe(1); expect(spy1.mock.calls).toHaveLength(1); - expect(spy2.mock.calls).toHaveLength(1); spy1.mockRestore(); obj.methodOne(); - obj.methodTwo(); - // After restoring `spy1` only the real method bumps its call count, not the spy. + // After restoring the spy only the real method bumps its call count, not the spy. expect(methodOneCalls).toBe(2); - expect(methodTwoCalls).toBe(2); expect(spy1.mock.calls).toHaveLength(0); - expect(spy2.mock.calls).toHaveLength(2); }); it('supports restoring all spies', () => { @@ -1679,43 +1653,30 @@ describe('moduleMocker', () => { it('supports restoring a spy on the prototype chain', () => { let methodOneCalls = 0; - let methodTwoCalls = 0; const prototype = { get methodOne() { return function () { methodOneCalls++; }; }, - get methodTwo() { - return function () { - methodTwoCalls++; - }; - }, }; const obj = Object.create(prototype, {}); const spy1 = moduleMocker.spyOn(obj, 'methodOne', 'get'); - const spy2 = moduleMocker.spyOn(obj, 'methodTwo', 'get'); obj.methodOne(); - obj.methodTwo(); - // Both spies and both original functions got called. + // The spy and the original function are called. expect(methodOneCalls).toBe(1); - expect(methodTwoCalls).toBe(1); expect(spy1.mock.calls).toHaveLength(1); - expect(spy2.mock.calls).toHaveLength(1); spy1.mockRestore(); obj.methodOne(); - obj.methodTwo(); - // After restoring `spy1` only the real method bumps its call count, not the spy. + // After restoring the spy only the real method bumps its call count, not the spy. expect(methodOneCalls).toBe(2); - expect(methodTwoCalls).toBe(2); expect(spy1.mock.calls).toHaveLength(0); - expect(spy2.mock.calls).toHaveLength(2); }); it('supports restoring all spies on the prototype chain', () => {