diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 4631cfefe342..623f5f7db55d 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -263,6 +263,12 @@ export function spyOn( if (originalDescriptor) { original = originalDescriptor[accessType] + // weird Proxy edge case where descriptor's value is undefined, + // but there's still a value on the object when called + // https://github.com/vitest-dev/vitest/issues/9439 + if (original == null && accessType === 'value') { + original = object[key] as unknown as Procedure + } } else if (accessType !== 'value') { original = () => object[key] diff --git a/test/core/test/mocking/vi-spyOn.test.ts b/test/core/test/mocking/vi-spyOn.test.ts index 602df0b5a39b..67a0279a9acb 100644 --- a/test/core/test/mocking/vi-spyOn.test.ts +++ b/test/core/test/mocking/vi-spyOn.test.ts @@ -1,61 +1,85 @@ import type { MockContext } from 'vitest' import { describe, expect, test, vi } from 'vitest' -test('vi.fn() has correct length', () => { - const fn0 = vi.spyOn({ fn: () => {} }, 'fn') - expect(fn0.length).toBe(0) +describe('vi.spyOn() edge cases', () => { + test('vi.spyOn() has correct length', () => { + const fn0 = vi.spyOn({ fn: () => {} }, 'fn') + expect(fn0.length).toBe(0) - const fnArgs = vi.spyOn({ fn: (..._args: any[]) => {} }, 'fn') - expect(fnArgs.length).toBe(0) + const fnArgs = vi.spyOn({ fn: (..._args: any[]) => {} }, 'fn') + expect(fnArgs.length).toBe(0) - const fn1 = vi.spyOn({ fn: (_arg1: any) => {} }, 'fn') - expect(fn1.length).toBe(1) + const fn1 = vi.spyOn({ fn: (_arg1: any) => {} }, 'fn') + expect(fn1.length).toBe(1) - const fn2 = vi.spyOn({ fn: (_arg1: any, _arg2: any) => {} }, 'fn') - expect(fn2.length).toBe(2) + const fn2 = vi.spyOn({ fn: (_arg1: any, _arg2: any) => {} }, 'fn') + expect(fn2.length).toBe(2) - const fn3 = vi.spyOn({ fn: (_arg1: any, _arg2: any, _arg3: any) => {} }, 'fn') - expect(fn3.length).toBe(3) -}) + const fn3 = vi.spyOn({ fn: (_arg1: any, _arg2: any, _arg3: any) => {} }, 'fn') + expect(fn3.length).toBe(3) + }) -describe('vi.spyOn() copies static properties', () => { - test('vi.spyOn() copies properties from functions', () => { - function a() {} - a.HELLO_WORLD = true - const obj = { - a, - } + test('can spy on a proxy with undefined descriptor\'s value', () => { + const obj = new Proxy<{ fn: () => number }>({} as any, { + get(_, prop) { + if (prop === 'fn') { + return () => 42 + } + }, + getOwnPropertyDescriptor(_, prop) { + if (prop === 'fn') { + return { + configurable: true, + enumerable: true, + value: undefined, + writable: true, + } + } + }, + }) + const spy = vi.spyOn(obj, 'fn') + expect(spy()).toBe(42) + }) - const spy = vi.spyOn(obj, 'a') + describe('vi.spyOn() copies static properties', () => { + test('vi.spyOn() copies properties from functions', () => { + function a() {} + a.HELLO_WORLD = true + const obj = { + a, + } - expect(obj.a.HELLO_WORLD).toBe(true) - expect(spy.HELLO_WORLD).toBe(true) - }) + const spy = vi.spyOn(obj, 'a') - test('vi.spyOn() copies properties from classes', () => { - class A { - static HELLO_WORLD = true - } - const obj = { - A, - } + expect(obj.a.HELLO_WORLD).toBe(true) + expect(spy.HELLO_WORLD).toBe(true) + }) - const spy = vi.spyOn(obj, 'A') + test('vi.spyOn() copies properties from classes', () => { + class A { + static HELLO_WORLD = true + } + const obj = { + A, + } - expect(obj.A.HELLO_WORLD).toBe(true) - expect(spy.HELLO_WORLD).toBe(true) - }) + const spy = vi.spyOn(obj, 'A') - test('vi.spyOn() ignores node.js.promisify symbol', () => { - const promisifySymbol = Symbol.for('nodejs.util.promisify.custom') - class Example { - static [promisifySymbol] = () => Promise.resolve(42) - } - const obj = { Example } + expect(obj.A.HELLO_WORLD).toBe(true) + expect(spy.HELLO_WORLD).toBe(true) + }) - const spy = vi.spyOn(obj, 'Example') + test('vi.spyOn() ignores node.js.promisify symbol', () => { + const promisifySymbol = Symbol.for('nodejs.util.promisify.custom') + class Example { + static [promisifySymbol] = () => Promise.resolve(42) + } + const obj = { Example } - expect(spy[promisifySymbol]).toBe(undefined) + const spy = vi.spyOn(obj, 'Example') + + expect(spy[promisifySymbol]).toBe(undefined) + }) }) })