diff --git a/docs/api/vi.md b/docs/api/vi.md index d3cc13a19..1a4772dfd 100644 --- a/docs/api/vi.md +++ b/docs/api/vi.md @@ -390,6 +390,33 @@ expect(res).toBe(5) expect(getApples).toHaveNthReturnedWith(2, 5) ``` +### vi.mockObject 3.2.0 + +- **Type:** `(value: T) => MaybeMockedDeep` + +Deeply mocks properties and methods of a given object in the same way as `vi.mock()` mocks module exports. See [automocking](/guide/mocking.html#automocking-algorithm) for the detail. + +```ts +const original = { + simple: () => 'value', + nested: { + method: () => 'real' + }, + prop: 'foo', +} + +const mocked = vi.mockObject(original) +expect(mocked.simple()).toBe(undefined) +expect(mocked.nested.method()).toBe(undefined) +expect(mocked.prop).toBe('foo') + +mocked.simple.mockReturnValue('mocked') +mocked.nested.method.mockReturnValue('mocked nested') + +expect(mocked.simple()).toBe('mocked') +expect(mocked.nested.method()).toBe('mocked nested') +``` + ### vi.isMockFunction - **Type:** `(fn: Function) => boolean` diff --git a/packages/vitest/src/integrations/vi.ts b/packages/vitest/src/integrations/vi.ts index 638cc5a4d..b2aa5b143 100644 --- a/packages/vitest/src/integrations/vi.ts +++ b/packages/vitest/src/integrations/vi.ts @@ -268,6 +268,37 @@ export interface VitestUtils { path: string ) => Promise> + /** + * Deeply mocks properties and methods of a given object + * in the same way as `vi.mock()` mocks module exports. + * + * @example + * ```ts + * const original = { + * simple: () => 'value', + * nested: { + * method: () => 'real' + * }, + * prop: 'foo', + * } + * + * const mocked = vi.mockObject(original) + * expect(mocked.simple()).toBe(undefined) + * expect(mocked.nested.method()).toBe(undefined) + * expect(mocked.prop).toBe('foo') + * + * mocked.simple.mockReturnValue('mocked') + * mocked.nested.method.mockReturnValue('mocked nested') + * + * expect(mocked.simple()).toBe('mocked') + * expect(mocked.nested.method()).toBe('mocked nested') + * ``` + * + * @param value - The object to be mocked + * @returns A deeply mocked version of the input object + */ + mockObject: (value: T) => MaybeMockedDeep + /** * Type helper for TypeScript. Just returns the object that was passed. * @@ -606,6 +637,10 @@ function createVitest(): VitestUtils { return _mocker().importMock(path, getImporter('importMock')) }, + mockObject(value: T) { + return _mocker().mockObject({ value }).value + }, + // this is typed in the interface so it's not necessary to type it here mocked(item: T, _options = {}): any { return item diff --git a/test/core/test/vi.spec.ts b/test/core/test/vi.spec.ts index a030fa6e0..2748c89a8 100644 --- a/test/core/test/vi.spec.ts +++ b/test/core/test/vi.spec.ts @@ -200,4 +200,38 @@ describe('testing vi utils', () => { expect(mod).toBeDefined() expect(mod.timeout).toBe(100) }) + + test('mockObject', () => { + const original = { + simple: () => 'value', + nested: { + method: () => 'real', + }, + prop: 'foo', + } + + const mocked = vi.mockObject(original) + expect(mocked.simple()).toBe(undefined) + expect(mocked.nested.method()).toBe(undefined) + expect(mocked.prop).toBe('foo') + mocked.simple.mockReturnValue('mocked') + mocked.nested.method.mockReturnValue('mocked nested') + expect(mocked.simple()).toBe('mocked') + expect(mocked.nested.method()).toBe('mocked nested') + + class OriginalClass { + constructor() { + throw new Error('should be mocked!') + } + + someFn() { + return 'value' + } + } + const MockedClass = vi.mockObject(OriginalClass) + const mockedInstance = new MockedClass() + expect(MockedClass).toHaveBeenCalled() + vi.mocked(mockedInstance).someFn.mockImplementation(() => 'mocked') + expect(mockedInstance.someFn()).toBe('mocked') + }) })