From f71004ffa379fc290031e628857ea4f3fb5d44c8 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 19 Feb 2025 10:06:13 +0900 Subject: [PATCH] fix(spy): clear/reset/restore mocks in stack order (#7499) --- packages/spy/src/index.ts | 25 ------------------------ packages/vitest/src/integrations/vi.ts | 6 +++--- test/core/test/jest-mock.test.ts | 27 +++++++++++++++++++++++--- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 16f6d52524ad..f6b0e838673b 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -412,7 +412,6 @@ export type Mocked = { : T[P]; } & T -const vitestSpy = Symbol.for('vitest.spy') export const mocks: Set = new Set() export function isMockFunction(fn: any): fn is MockInstance { @@ -421,20 +420,6 @@ export function isMockFunction(fn: any): fn is MockInstance { ) } -function getSpy( - obj: unknown, - method: keyof any, - accessType?: 'get' | 'set', -): MockInstance | undefined { - const desc = Object.getOwnPropertyDescriptor(obj, method) - if (desc) { - const fn = desc[accessType ?? 'value'] - if (typeof fn === 'function' && vitestSpy in fn) { - return fn - } - } -} - export function spyOn>>( obj: T, methodName: S, @@ -464,11 +449,6 @@ export function spyOn( } as const const objMethod = accessType ? { [dictionary[accessType]]: method } : method - const currentStub = getSpy(obj, method, accessType) - if (currentStub) { - return currentStub - } - const stub = tinyspy.internalSpyOn(obj, objMethod as any) return enhanceSpy(stub) as MockInstance @@ -542,11 +522,6 @@ function enhanceSpy( let name: string = (stub as any).name - Object.defineProperty(stub, vitestSpy, { - value: true, - enumerable: false, - }) - stub.getMockName = () => name || 'vi.fn()' stub.mockName = (n) => { name = n diff --git a/packages/vitest/src/integrations/vi.ts b/packages/vitest/src/integrations/vi.ts index 135365cb5eee..3cb30894e5e6 100644 --- a/packages/vitest/src/integrations/vi.ts +++ b/packages/vitest/src/integrations/vi.ts @@ -616,17 +616,17 @@ function createVitest(): VitestUtils { }, clearAllMocks() { - mocks.forEach(spy => spy.mockClear()) + [...mocks].reverse().forEach(spy => spy.mockClear()) return utils }, resetAllMocks() { - mocks.forEach(spy => spy.mockReset()) + [...mocks].reverse().forEach(spy => spy.mockReset()) return utils }, restoreAllMocks() { - mocks.forEach(spy => spy.mockRestore()) + [...mocks].reverse().forEach(spy => spy.mockRestore()) return utils }, diff --git a/test/core/test/jest-mock.test.ts b/test/core/test/jest-mock.test.ts index 03026c80c292..17dbcd2ec3e2 100644 --- a/test/core/test/jest-mock.test.ts +++ b/test/core/test/jest-mock.test.ts @@ -363,7 +363,7 @@ describe('jest mock compat layer', () => { expect(obj.property).toBe(true) }) - it('spyOn returns the same spy twice', () => { + it('spyOn multiple times', () => { const obj = { method() { return 'original' @@ -375,7 +375,7 @@ describe('jest mock compat layer', () => { expect(vi.isMockFunction(obj.method)).toBe(true) expect(obj.method()).toBe('mocked') - expect(spy1).toBe(spy2) + expect(spy1).not.toBe(spy2) spy2.mockImplementation(() => 'mocked2') @@ -383,11 +383,32 @@ describe('jest mock compat layer', () => { spy2.mockRestore() - expect(obj.method()).toBe('original') + expect(obj.method()).toBe('mocked') + expect(vi.isMockFunction(obj.method)).toBe(true) + expect(obj.method).toBe(spy1) + + spy1.mockRestore() expect(vi.isMockFunction(obj.method)).toBe(false) expect(obj.method).not.toBe(spy1) }) + it('restoreAllMocks in stack order', () => { + const obj = { foo: () => 'foo' } + + vi.spyOn(obj, 'foo').mockImplementation(() => 'mocked1') + expect(obj.foo()).toBe('mocked1') + expect(vi.isMockFunction(obj.foo)).toBe(true) + + vi.spyOn(obj, 'foo').mockImplementation(() => 'mocked2') + expect(obj.foo()).toBe('mocked2') + expect(vi.isMockFunction(obj.foo)).toBe(true) + + vi.restoreAllMocks() + + expect(obj.foo()).toBe('foo') + expect(vi.isMockFunction(obj.foo)).toBe(false) + }) + it('should spy on property setter (2), and mockReset should not restore original descriptor', () => { const obj = { _property: false,