Skip to content

Commit

Permalink
fix(spy): clear/reset/restore mocks in stack order (#7499)
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa authored Feb 19, 2025
1 parent 1d9a6f4 commit f71004f
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 31 deletions.
25 changes: 0 additions & 25 deletions packages/spy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,6 @@ export type Mocked<T> = {
: T[P];
} & T

const vitestSpy = Symbol.for('vitest.spy')
export const mocks: Set<MockInstance> = new Set()

export function isMockFunction(fn: any): fn is MockInstance {
Expand All @@ -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<T, S extends Properties<Required<T>>>(
obj: T,
methodName: S,
Expand Down Expand Up @@ -464,11 +449,6 @@ export function spyOn<T, K extends keyof T>(
} 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
Expand Down Expand Up @@ -542,11 +522,6 @@ function enhanceSpy<T extends Procedure>(

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
Expand Down
6 changes: 3 additions & 3 deletions packages/vitest/src/integrations/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
},

Expand Down
27 changes: 24 additions & 3 deletions test/core/test/jest-mock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -375,19 +375,40 @@ 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')

expect(obj.method()).toBe('mocked2')

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,
Expand Down

0 comments on commit f71004f

Please sign in to comment.