diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 0d7bf3e90..16b15e757 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -260,8 +260,16 @@ export function spyOn( original = object[key] as unknown as Procedure } - if (isMockFunction(original)) { - return original + const originalImplementation = ssr && original ? original() : original + const originalType = typeof originalImplementation + + assert( + originalType === 'function', + `vi.spyOn() can only spy on a function. Received ${originalType}.`, + ) + + if (isMockFunction(originalImplementation)) { + return originalImplementation } const reassign = (cb: any) => { @@ -292,7 +300,7 @@ export function spyOn( const mock = createMockInstance({ restore, - originalImplementation: ssr && original ? original() : original, + originalImplementation, resetToMockName: true, }) diff --git a/test/core/test/mocking/vi-spyOn.test.ts b/test/core/test/mocking/vi-spyOn.test.ts index 5b0573617..dec205318 100644 --- a/test/core/test/mocking/vi-spyOn.test.ts +++ b/test/core/test/mocking/vi-spyOn.test.ts @@ -592,6 +592,56 @@ describe('vi.spyOn() restoration', () => { }) }) +describe('vi.spyOn() on Vite SSR', () => { + test('vi.spyOn() throws an error if a getter returns a non-function value in SSR', () => { + const module = { + get primitive() { + return 42 + }, + } + expect(() => { + // @ts-expect-error types recognize it's not a function + vi.spyOn(module, 'primitive') + }).toThrowError('vi.spyOn() can only spy on a function. Received number.') + }) + + test('vi.spyOn() assigns the method on a getter', () => { + const method = () => {} + const module = { + get method() { + return method + }, + } + const spy = vi.spyOn(module, 'method') + expect(spy.getMockImplementation()).toBe(undefined) + + module.method() + expect(spy.mock.calls).toEqual([[]]) + expect(module.method).toBe(spy) + + spy.mockRestore() + expect(module.method).toBe(method) + }) + + test('vi.spyOn() can reassign the SSR getter method', () => { + const method = () => {} + const module = { + get method() { + return method + }, + } + const spy1 = vi.spyOn(module, 'method') + const spy2 = vi.spyOn(module, 'method') + expect(vi.isMockFunction(spy1)).toBe(true) + expect(vi.isMockFunction(spy2)).toBe(true) + expect(spy1).toBe(spy2) + + module.method() + expect(spy1.mock.calls).toEqual([[]]) + expect(spy2.mock.calls).toEqual([[]]) + }) +}) + function assertStateEmpty(state: MockContext) { expect(state.calls).toHaveLength(0) expect(state.results).toHaveLength(0)