From 628ab32f18636c3f82ac6882e5338d8f833c2938 Mon Sep 17 00:00:00 2001 From: Rayan Salhab Date: Thu, 30 Apr 2026 10:01:34 +0300 Subject: [PATCH] fix(spy): support private method spy types (fix #10172) (#10213) --- packages/spy/src/index.ts | 26 ++++++++++++++++++++++---- test/typescript/test-d/test.test-d.ts | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index bd50f730b..125724845 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -259,6 +259,20 @@ export function fn( }) as Mock } +type SpyOnValue = K extends keyof Required + ? Required[K] + : (T & Record)[K] + +type SpyOnMethod + = SpyOnValue extends Constructable | Procedure + ? SpyOnValue + : never + +type SpyOnMethodKey + = SpyOnValue extends Constructable | Procedure + ? K + : never + export function spyOn>>( object: T, key: S, @@ -275,7 +289,11 @@ export function spyOn> | Methods ): Required[M] extends Constructable | Procedure ? Mock[M]> : never -export function spyOn( +export function spyOn( + object: T, + key: SpyOnMethodKey, +): Mock> +export function spyOn( object: T, key: K, accessor?: 'get' | 'set', @@ -317,14 +335,14 @@ export function spyOn( // 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 + original = object[key as unknown as keyof T] as unknown as Procedure } } else if (accessType !== 'value') { - original = () => object[key] + original = () => object[key as unknown as keyof T] } else { - original = object[key] as unknown as Procedure + original = object[key as unknown as keyof T] as unknown as Procedure } const originalImplementation = ssr && original ? original() : original diff --git a/test/typescript/test-d/test.test-d.ts b/test/typescript/test-d/test.test-d.ts index c940a29b9..56e6e2a93 100644 --- a/test/typescript/test-d/test.test-d.ts +++ b/test/typescript/test-d/test.test-d.ts @@ -44,6 +44,28 @@ describe('test', () => { // @ts-expect-error vi.spyOn(google, 'sheets').mockReturnValue({ foo: 1234 }) }) + + test('spyOn private and protected methods compiles', () => { + class TestClass { + private privateMethod(): number { + return 42 + } + + protected protectedMethod(): string { + return '42' + } + } + + const instance = new TestClass() + vi.spyOn(instance, 'privateMethod').mockReturnValue(1) + vi.spyOn(instance, 'protectedMethod').mockReturnValue('1') + // @ts-expect-error private method returns number + vi.spyOn(instance, 'privateMethod').mockReturnValue('1') + // @ts-expect-error protected method returns string + vi.spyOn(instance, 'protectedMethod').mockReturnValue(1) + // @ts-expect-error unknown methods are rejected + vi.spyOn(instance, 'unknownMethod') + }) }) expectTypeOf({ wolk: 'true' }).toHaveProperty('wolk') -- 2.51.2