Skip to content

Commit 95f0203

Browse files
authoredAug 21, 2024··
feat(mocker): introduce @vitest/mocker package, allow { spy: true } instead of a factory (#6289)
1 parent 15ec2fb commit 95f0203

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+3443
-1991
lines changed
 

‎docs/api/vi.md

+41-8
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ This section describes the API that you can use when [mocking a module](/guide/m
1616

1717
### vi.mock
1818

19-
- **Type**: `(path: string, factory?: (importOriginal: () => unknown) => unknown) => void`
20-
- **Type**: `<T>(path: Promise<T>, factory?: (importOriginal: () => T) => T | Promise<T>) => void`
19+
- **Type**: `(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void`
20+
- **Type**: `<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void`
2121

2222
Substitutes all imported modules from provided `path` with another module. You can use configured Vite aliases inside a path. The call to `vi.mock` is hoisted, so it doesn't matter where you call it. It will always be executed before all imports. If you need to reference some variables outside of its scope, you can define them inside [`vi.hoisted`](#vi-hoisted) and reference them inside `vi.mock`.
2323

@@ -29,11 +29,27 @@ In order to hoist `vi.mock`, Vitest statically analyzes your files. It indicates
2929
Vitest will not mock modules that were imported inside a [setup file](/config/#setupfiles) because they are cached by the time a test file is running. You can call [`vi.resetModules()`](#vi-resetmodules) inside [`vi.hoisted`](#vi-hoisted) to clear all module caches before running a test file.
3030
:::
3131

32-
If `factory` is defined, all imports will return its result. Vitest calls factory only once and caches results for all subsequent imports until [`vi.unmock`](#vi-unmock) or [`vi.doUnmock`](#vi-dounmock) is called.
32+
If the `factory` function is defined, all imports will return its result. Vitest calls factory only once and caches results for all subsequent imports until [`vi.unmock`](#vi-unmock) or [`vi.doUnmock`](#vi-dounmock) is called.
3333

3434
Unlike in `jest`, the factory can be asynchronous. You can use [`vi.importActual`](#vi-importactual) or a helper with the factory passed in as the first argument, and get the original module inside.
3535

36-
Vitest also supports a module promise instead of a string in the `vi.mock` and `vi.doMock` methods for better IDE support. When the file is moved, the path will be updated, and `importOriginal` also inherits the type automatically. Using this signature will also enforce factory return type to be compatible with the original module (but every export is optional).
36+
Since Vitest 2.1, you can also provide an object with a `spy` property instead of a factory function. If `spy` is `true`, then Vitest will automock the module as usual, but it won't override the implementation of exports. This is useful if you just want to assert that the exported method was called correctly by another method.
37+
38+
```ts
39+
import { calculator } from './src/calculator.ts'
40+
41+
vi.mock('./src/calculator.ts', { spy: true })
42+
43+
// calls the original implementation,
44+
// but allows asserting the behaviour later
45+
const result = calculator(1, 2)
46+
47+
expect(result).toBe(3)
48+
expect(calculator).toHaveBeenCalledWith(1, 2)
49+
expect(calculator).toHaveReturned(3)
50+
```
51+
52+
Vitest also supports a module promise instead of a string in the `vi.mock` and `vi.doMock` methods for better IDE support. When the file is moved, the path will be updated, and `importOriginal` inherits the type automatically. Using this signature will also enforce factory return type to be compatible with the original module (keeping exports optional).
3753

3854
```ts twoslash
3955
// @filename: ./path/to/module.js
@@ -103,7 +119,7 @@ vi.mock('./path/to/module.js', () => {
103119
```
104120
:::
105121

106-
If there is a `__mocks__` folder alongside a file that you are mocking, and the factory is not provided, Vitest will try to find a file with the same name in the `__mocks__` subfolder and use it as an actual module. If you are mocking a dependency, Vitest will try to find a `__mocks__` folder in the [root](/config/#root) of the project (default is `process.cwd()`). You can tell Vitest where the dependencies are located through the [deps.moduleDirectories](/config/#deps-moduledirectories) config option.
122+
If there is a `__mocks__` folder alongside a file that you are mocking, and the factory is not provided, Vitest will try to find a file with the same name in the `__mocks__` subfolder and use it as an actual module. If you are mocking a dependency, Vitest will try to find a `__mocks__` folder in the [root](/config/#root) of the project (default is `process.cwd()`). You can tell Vitest where the dependencies are located through the [`deps.moduleDirectories`](/config/#deps-moduledirectories) config option.
107123

108124
For example, you have this file structure:
109125

@@ -118,7 +134,7 @@ For example, you have this file structure:
118134
- increment.test.js
119135
```
120136

121-
If you call `vi.mock` in a test file without a factory provided, it will find a file in the `__mocks__` folder to use as a module:
137+
If you call `vi.mock` in a test file without a factory or options provided, it will find a file in the `__mocks__` folder to use as a module:
122138

123139
```ts
124140
// increment.test.js
@@ -144,8 +160,8 @@ If there is no `__mocks__` folder or a factory provided, Vitest will import the
144160

145161
### vi.doMock
146162

147-
- **Type**: `(path: string, factory?: (importOriginal: () => unknown) => unknown) => void`
148-
- **Type**: `<T>(path: Promise<T>, factory?: (importOriginal: () => T) => T | Promise<T>) => void`
163+
- **Type**: `(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void`
164+
- **Type**: `<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void`
149165

150166
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.
151167

@@ -418,6 +434,23 @@ console.log(cart.getApples()) // still 42!
418434
```
419435
:::
420436

437+
::: tip
438+
It is not possible to spy on a specific exported method in [Browser Mode](/guide/browser/). Instead, you can spy on every exported method by calling `vi.mock("./file-path.js", { spy: true })`. This will mock every export but keep its implementation intact, allowing you to assert if the method was called correctly.
439+
440+
```ts
441+
import { calculator } from './src/calculator.ts'
442+
443+
vi.mock('./src/calculator.ts', { spy: true })
444+
445+
calculator(1, 2)
446+
447+
expect(calculator).toHaveBeenCalledWith(1, 2)
448+
expect(calculator).toHaveReturned(3)
449+
```
450+
451+
And while it is possible to spy on exports in `jsdom` or other Node.js environments, this might change in the future.
452+
:::
453+
421454
### vi.stubEnv {#vi-stubenv}
422455

423456
- **Type:** `(name: string, value: string) => Vitest`

‎eslint.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export default antfu(
9797
{
9898
files: [
9999
`docs/${GLOB_SRC}`,
100+
`**/*.md`,
100101
],
101102
rules: {
102103
'style/max-statements-per-line': 'off',

0 commit comments

Comments
 (0)
Please sign in to comment.