Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion docs/api/vi.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ function mocked<T>(

Type helper for TypeScript. Just returns the object that was passed.

When `partial` is `true` it will expect a `Partial<T>` as a return value. By default, this will only make TypeScript believe that the first level values are mocked. You can pass down `{ deep: true }` as a second argument to tell TypeScript that the whole object is mocked, if it actually is.
When `partial` is `true` it will expect a `Partial<T>` as a return value. By default, this will only make TypeScript believe that the first level values are mocked. You can pass down `{ deep: true }` as a second argument to tell TypeScript that the whole object is mocked, if it actually is. You can pass down `{ partial: true, deep: true }` to make nested objects also partial recursively.

```ts [example.ts]
export function add(x: number, y: number): number {
Expand All @@ -272,6 +272,10 @@ export function add(x: number, y: number): number {
export function fetchSomething(): Promise<Response> {
return fetch('https://vitest.dev/')
}

export function getUser(): { name: string; address: { city: string; zip: string } } {
return { name: 'John', address: { city: 'New York', zip: '10001' } }
}
```

```ts [example.test.ts]
Expand All @@ -289,6 +293,13 @@ test('mock return value with only partially correct typing', async () => {
vi.mocked(example.fetchSomething, { partial: true }).mockResolvedValue({ ok: false })
// vi.mocked(example.someFn).mockResolvedValue({ ok: false }) // this is a type error
})

test('mock return value with deep partial typing', async () => {
vi.mocked(example.getUser, { partial: true, deep: true }).mockReturnValue({
address: { city: 'Los Angeles' },
})
expect(example.getUser().address.city).toBe('Los Angeles')
})
```

### vi.importActual
Expand Down
33 changes: 32 additions & 1 deletion packages/spy/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,37 @@ export type PartialMock<T extends Procedure | Constructable = Procedure> = Mock<
>
>

type DeepPartial<T> = T extends Procedure
? T
: T extends Array<infer U>
? Array<DeepPartial<U>>
: T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T

type DeepPartialMaybePromise<T> = T extends Promise<Awaited<T>>
? Promise<DeepPartial<Awaited<T>>>
: DeepPartial<T>

type DeepPartialResultFunction<T> = T extends Constructable
? ({
new (...args: ConstructorParameters<T>): InstanceType<T>
})
| ({
(this: InstanceType<T>, ...args: ConstructorParameters<T>): void
})
: T extends Procedure
? (...args: Parameters<T>) => DeepPartialMaybePromise<ReturnType<T>>
: T

type DeepPartialMock<T extends Procedure | Constructable = Procedure> = Mock<
DeepPartialResultFunction<
T extends Mock
? NonNullable<ReturnType<T['getMockImplementation']>>
: T
>
>

export type MaybeMockedConstructor<T> = T extends Constructable
? Mock<T>
: T
Expand All @@ -417,7 +448,7 @@ export type PartiallyMockedFunction<T extends Procedure | Constructable> = Parti
}
export type MockedFunctionDeep<T extends Procedure | Constructable> = Mock<T>
& MockedObjectDeep<T>
export type PartiallyMockedFunctionDeep<T extends Procedure | Constructable> = PartialMock<T>
export type PartiallyMockedFunctionDeep<T extends Procedure | Constructable> = DeepPartialMock<T>
& MockedObjectDeep<T>
export type MockedObject<T> = MaybeMockedConstructor<T> & {
[K in Methods<T>]: T[K] extends Procedure ? MockedFunction<T[K]> : T[K];
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/integrations/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ export interface VitestUtils {
* Type helper for TypeScript. Just returns the object that was passed.
*
* When `partial` is `true` it will expect a `Partial<T>` as a return value. By default, this will only make TypeScript believe that
* the first level values are mocked. You can pass down `{ deep: true }` as a second argument to tell TypeScript that the whole object is mocked, if it actually is.
* the first level values are mocked. You can pass down `{ partial: true, deep: true }` to make nested objects also partial recursively.
* @example
* ```ts
* import example from './example.js'
Expand Down
34 changes: 34 additions & 0 deletions test/core/test/vi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,40 @@ describe('testing vi utils', () => {
vi.mocked(fetchSomething).mockResolvedValue(new Response(null))
vi.mocked(fetchSomething, { partial: true }).mockResolvedValue({ ok: false })
}

// #8152
if (0) {
interface NestedObject {
level1: {
level2: {
value: string
count: number
}
name: string
}
items: string[]
}

const mockNestedFactory = vi.fn<() => NestedObject>()

vi.mocked(mockNestedFactory, { partial: true, deep: true }).mockReturnValue({
level1: { level2: {} },
})
vi.mocked(mockNestedFactory, { partial: true, deep: true }).mockReturnValue({
level1: {},
})
vi.mocked(mockNestedFactory, { partial: true, deep: true }).mockReturnValue({})
vi.mocked(mockNestedFactory, { partial: true, deep: true }).mockReturnValue({
items: ['a', 'b'],
})

const mockNestedAsyncFactory = vi.fn<() => Promise<NestedObject>>()

vi.mocked(mockNestedAsyncFactory, { partial: true, deep: true }).mockResolvedValue({
level1: { level2: {} },
})
vi.mocked(mockNestedAsyncFactory, { partial: true, deep: true }).mockResolvedValue({})
}
})

test('vi.mocked with classes', () => {
Expand Down
Loading