Skip to content

Commit f87568d

Browse files
authored
fix(spy): allow classes in vi.mocked utility (#8839)
1 parent c0f0c34 commit f87568d

File tree

2 files changed

+86
-37
lines changed

2 files changed

+86
-37
lines changed

packages/spy/src/types.ts

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -370,28 +370,36 @@ type PartialMaybePromise<T> = T extends Promise<Awaited<T>>
370370
? Promise<Partial<Awaited<T>>>
371371
: Partial<T>
372372

373-
export interface PartialMock<T extends Procedure = Procedure>
374-
extends MockInstance<
375-
(...args: Parameters<T>) => PartialMaybePromise<ReturnType<T>>
376-
> {
377-
new (...args: Parameters<T>): ReturnType<T>
378-
(...args: Parameters<T>): ReturnType<T>
379-
}
373+
type PartialResultFunction<T> = T extends Constructable
374+
? ({
375+
new (...args: ConstructorParameters<T>): InstanceType<T>
376+
})
377+
| ({
378+
(this: InstanceType<T>, ...args: ConstructorParameters<T>): void
379+
})
380+
: T extends Procedure
381+
? (...args: Parameters<T>) => PartialMaybePromise<ReturnType<T>>
382+
: T
383+
384+
export interface PartialMock<T extends Procedure | Constructable = Procedure>
385+
extends Mock<
386+
PartialResultFunction<T extends Mock
387+
? NonNullable<ReturnType<T['getMockImplementation']>>
388+
: T>
389+
> {}
380390

381-
export type MaybeMockedConstructor<T> = T extends new (
382-
...args: Array<any>
383-
) => infer R
384-
? Mock<(...args: ConstructorParameters<T>) => R>
391+
export type MaybeMockedConstructor<T> = T extends Constructable
392+
? Mock<T>
385393
: T
386-
export type MockedFunction<T extends Procedure> = Mock<T> & {
394+
export type MockedFunction<T extends Procedure | Constructable> = Mock<T> & {
387395
[K in keyof T]: T[K];
388396
}
389-
export type PartiallyMockedFunction<T extends Procedure> = PartialMock<T> & {
397+
export type PartiallyMockedFunction<T extends Procedure | Constructable> = PartialMock<T> & {
390398
[K in keyof T]: T[K];
391399
}
392-
export type MockedFunctionDeep<T extends Procedure> = Mock<T>
400+
export type MockedFunctionDeep<T extends Procedure | Constructable> = Mock<T>
393401
& MockedObjectDeep<T>
394-
export type PartiallyMockedFunctionDeep<T extends Procedure> = PartialMock<T>
402+
export type PartiallyMockedFunctionDeep<T extends Procedure | Constructable> = PartialMock<T>
395403
& MockedObjectDeep<T>
396404
export type MockedObject<T> = MaybeMockedConstructor<T> & {
397405
[K in Methods<T>]: T[K] extends Procedure ? MockedFunction<T[K]> : T[K];
@@ -400,25 +408,25 @@ export type MockedObjectDeep<T> = MaybeMockedConstructor<T> & {
400408
[K in Methods<T>]: T[K] extends Procedure ? MockedFunctionDeep<T[K]> : T[K];
401409
} & { [K in Properties<T>]: MaybeMockedDeep<T[K]> }
402410

403-
export type MaybeMockedDeep<T> = T extends Procedure
411+
export type MaybeMockedDeep<T> = T extends Procedure | Constructable
404412
? MockedFunctionDeep<T>
405413
: T extends object
406414
? MockedObjectDeep<T>
407415
: T
408416

409-
export type MaybePartiallyMockedDeep<T> = T extends Procedure
417+
export type MaybePartiallyMockedDeep<T> = T extends Procedure | Constructable
410418
? PartiallyMockedFunctionDeep<T>
411419
: T extends object
412420
? MockedObjectDeep<T>
413421
: T
414422

415-
export type MaybeMocked<T> = T extends Procedure
423+
export type MaybeMocked<T> = T extends Procedure | Constructable
416424
? MockedFunction<T>
417425
: T extends object
418426
? MockedObject<T>
419427
: T
420428

421-
export type MaybePartiallyMocked<T> = T extends Procedure
429+
export type MaybePartiallyMocked<T> = T extends Procedure | Constructable
422430
? PartiallyMockedFunction<T>
423431
: T extends object
424432
? MockedObject<T>
@@ -428,9 +436,7 @@ export interface Constructable {
428436
new (...args: any[]): any
429437
}
430438

431-
export type MockedClass<T extends Constructable> = MockInstance<
432-
(...args: ConstructorParameters<T>) => InstanceType<T>
433-
> & {
439+
export type MockedClass<T extends Constructable> = MockInstance<T> & {
434440
prototype: T extends { prototype: any } ? Mocked<T['prototype']> : never
435441
} & T
436442

test/core/test/vi.spec.ts

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @vitest-environment jsdom
33
*/
44

5-
import type { Mock, MockedFunction, MockedObject, MockInstance } from 'vitest'
5+
import type { Mock, Mocked, MockedFunction, MockedObject, MockInstance } from 'vitest'
66
import { describe, expect, expectTypeOf, test, vi } from 'vitest'
77
import { getWorkerState } from '../../../packages/vitest/src/runtime/utils'
88

@@ -56,33 +56,49 @@ describe('testing vi utils', () => {
5656
baz: string
5757
}
5858

59-
const mockFactory = vi.fn<() => FooBar>()
59+
const mockFnFactory = vi.fn<() => FooBar>()
6060

61-
vi.mocked(mockFactory, { partial: true }).mockReturnValue({
61+
vi.mocked(mockFnFactory, { partial: true }).mockReturnValue({
6262
foo: vi.fn(),
6363
})
6464

65-
vi.mocked(mockFactory, { partial: true, deep: false }).mockReturnValue({
65+
vi.mocked(mockFnFactory, { partial: true, deep: false }).mockReturnValue({
6666
bar: vi.fn<FooBar['bar']>(),
6767
})
6868

69-
vi.mocked(mockFactory, { partial: true, deep: true }).mockReturnValue({
69+
vi.mocked(mockFnFactory, { partial: true, deep: true }).mockReturnValue({
7070
baz: 'baz',
7171
})
7272

73-
const mockFactoryAsync = vi.fn<() => Promise<FooBar>>()
73+
if (0) {
74+
const mockFactory = (): FooBar => ({} as FooBar)
7475

75-
vi.mocked(mockFactoryAsync, { partial: true }).mockResolvedValue({
76-
foo: vi.fn(),
77-
})
76+
vi.mocked(mockFactory, { partial: true }).mockReturnValue({
77+
foo: vi.fn(),
78+
})
7879

79-
vi.mocked(mockFactoryAsync, { partial: true, deep: false }).mockResolvedValue({
80-
bar: vi.fn<FooBar['bar']>(),
81-
})
80+
vi.mocked(mockFactory, { partial: true, deep: false }).mockReturnValue({
81+
bar: vi.fn<FooBar['bar']>(),
82+
})
8283

83-
vi.mocked(mockFactoryAsync, { partial: true, deep: true }).mockResolvedValue({
84-
baz: 'baz',
85-
})
84+
vi.mocked(mockFactory, { partial: true, deep: true }).mockReturnValue({
85+
baz: 'baz',
86+
})
87+
88+
const mockFactoryAsync = async (): Promise<FooBar> => ({} as FooBar)
89+
90+
vi.mocked(mockFactoryAsync, { partial: true }).mockResolvedValue({
91+
foo: vi.fn(),
92+
})
93+
94+
vi.mocked(mockFactoryAsync, { partial: true, deep: false }).mockResolvedValue({
95+
bar: vi.fn<FooBar['bar']>(),
96+
})
97+
98+
vi.mocked(mockFactoryAsync, { partial: true, deep: true }).mockResolvedValue({
99+
baz: 'baz',
100+
})
101+
}
86102

87103
function fetchSomething(): Promise<Response> {
88104
return fetch('https://vitest.dev/')
@@ -94,6 +110,33 @@ describe('testing vi utils', () => {
94110
}
95111
})
96112

113+
test('vi.mocked with classes', () => {
114+
class Foo {
115+
constructor(public readonly bar: string) {}
116+
117+
public getBar(): string {
118+
return this.bar
119+
}
120+
}
121+
class FooMock implements Mocked<Foo> {
122+
readonly barMock: Mock<() => string> = vi.fn()
123+
124+
public get bar(): string {
125+
return this.barMock()
126+
}
127+
128+
public getBar: Mock<() => string> = vi
129+
.fn()
130+
.mockImplementation(() => this.barMock())
131+
}
132+
133+
// type check only
134+
if (0) {
135+
vi.mocked(Foo).mockImplementation(FooMock)
136+
vi.mocked(Foo).mockImplementation(Foo)
137+
}
138+
})
139+
97140
test('vi.fn and Mock type', () => {
98141
// use case from https://github.com/vitest-dev/vitest/issues/4723#issuecomment-1851034249
99142

0 commit comments

Comments
 (0)