diff --git a/docs/api/vi.md b/docs/api/vi.md index 9a5277f1bfe3..ed3a47ea5a84 100644 --- a/docs/api/vi.md +++ b/docs/api/vi.md @@ -179,11 +179,11 @@ If there is no `__mocks__` folder or a factory provided, Vitest will import the function doMock( path: string, factory?: MockOptions | MockFactory -): void +): Disposable function doMock( module: Promise, factory?: MockOptions | MockFactory -): void +): Disposable ``` The same as [`vi.mock`](#vi-mock), but it's not hoisted to the top of the file, so you can reference variables in the global file scope. The next [dynamic import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) of the module will be mocked. @@ -229,6 +229,24 @@ test('importing the next module imports mocked one', async () => { }) ``` +::: tip +In environments that support [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management), you can use `using` on the value returned from `vi.doMock()` to automatically call [`vi.doUnmock()`](#vi-dounmock) on the mocked module when the containing block is exited. This is especially useful when mocking a dynamically imported module for a single test case. + +```ts +it('uses a mocked version of my-module', () => { + using _mockDisposable = vi.doMock('my-module') + + const myModule = await import('my-module') // mocked + + // my-module is restored here +}) + +it('uses the normal version of my-module again', () => { + const myModule = await import('my-module') // not mocked +}) +``` +::: + ### vi.mocked ```ts diff --git a/packages/vitest/src/integrations/vi.ts b/packages/vitest/src/integrations/vi.ts index 416a8c3bfa3c..89dc544f5611 100644 --- a/packages/vitest/src/integrations/vi.ts +++ b/packages/vitest/src/integrations/vi.ts @@ -220,11 +220,13 @@ export interface VitestUtils { * Mocking algorithm is described in [documentation](https://vitest.dev/guide/mocking/modules). * @param path Path to the module. Can be aliased, if your Vitest config supports it * @param factory Mocked module factory. The result of this function will be an exports object + * + * @returns A disposable object that calls {@link doUnmock()} when disposed */ // eslint-disable-next-line ts/method-signature-style - doMock(path: string, factory?: MockFactoryWithHelper | MockOptions): void + doMock(path: string, factory?: MockFactoryWithHelper | MockOptions): Disposable // eslint-disable-next-line ts/method-signature-style - doMock(module: Promise, factory?: MockFactoryWithHelper | MockOptions): void + doMock(module: Promise, factory?: MockFactoryWithHelper | MockOptions): Disposable /** * Removes module from mocked registry. All subsequent calls to import will return original module. * @@ -617,6 +619,14 @@ function createVitest(): VitestUtils { ) : factory, ) + + const rv = {} as Disposable + if (Symbol.dispose) { + rv[Symbol.dispose] = () => { + _mocker().queueUnmock(path, importer) + } + } + return rv }, doUnmock(path: string | Promise) { diff --git a/test/core/test/do-mock.test.ts b/test/core/test/do-mock.test.ts index 4cec6e845ac7..8026bbbebbee 100644 --- a/test/core/test/do-mock.test.ts +++ b/test/core/test/do-mock.test.ts @@ -30,3 +30,35 @@ test('the second doMock can override the first doMock', async () => { expect(incrementWith20(1)).toBe(21) }) + +test.runIf(Symbol.dispose)('doMock works with using', async () => { + vi.doUnmock('./fixtures/increment') + + { + const { increment: incrementWith1 } = await import('./fixtures/increment') + expect(incrementWith1(1)).toBe(2) + } + + { + using _incrementMock = vi.doMock('./fixtures/increment', () => ({ + increment: (num: number) => num + 10, + })) + + const { increment: incrementWith10 } = await import('./fixtures/increment') + + expect(incrementWith10(1)).toBe(11) + } + + { + const { increment: incrementWith1 } = await import('./fixtures/increment') + expect(incrementWith1(1)).toBe(2) + } +}) + +test.skipIf(Symbol.dispose)('doMock works with using', async () => { + const _incrementMock = vi.doMock('./fixtures/increment', () => ({ + increment: (num: number) => num + 10, + })) + + expect(_incrementMock?.[Symbol.dispose]).toBeUndefined() +})