|
| 1 | +# Using as a Vite plugin |
| 2 | + |
| 3 | +Make sure you have `vite` and `@vitest/spy` installed (and `msw` if you are planning to use `ModuleMockerMSWInterceptor`). |
| 4 | + |
| 5 | +```ts |
| 6 | +import { mockerPlugin } from '@vitest/mocker/node' |
| 7 | + |
| 8 | +export default defineConfig({ |
| 9 | + plugins: [mockerPlugin()], |
| 10 | +}) |
| 11 | +``` |
| 12 | + |
| 13 | +To use it in your code, register the runtime mocker. The naming of `vi` matters - it is used by the compiler. You can configure the name by changing the `hoistMocks.utilsObjectName` and `hoistMocks.regexpHoistable` options. |
| 14 | + |
| 15 | +```ts |
| 16 | +import { |
| 17 | + ModuleMockerMSWInterceptor, |
| 18 | + ModuleMockerServerInterceptor, |
| 19 | + registerModuleMocker |
| 20 | +} from '@vitest/mocker/register' |
| 21 | + |
| 22 | +// you can use either a server interceptor (relies on Vite's websocket connection) |
| 23 | +const vi = registerModuleMocker(new ModuleMockerServerInterceptor()) |
| 24 | +// or you can use MSW to intercept requests directly in the browser |
| 25 | +const vi = registerModuleMocker(new ModuleMockerMSWInterceptor()) |
| 26 | +``` |
| 27 | + |
| 28 | +```ts |
| 29 | +// you can also just import "auto-register" at the top of your entry point, |
| 30 | +// this will use the server interceptor by default |
| 31 | +import '@vitest/mocker/auto-register' |
| 32 | +// if you do this, you can create compiler hints with "createCompilerHints" |
| 33 | +// utility to use in your own code |
| 34 | +import { createCompilerHints } from '@vitest/mocker/browser' |
| 35 | +const vi = createCompilerHints() |
| 36 | +``` |
| 37 | + |
| 38 | +`registerModuleMocker` returns compiler hints that Vite plugin will look for. |
| 39 | + |
| 40 | +By default, Vitest looks for `vi.mock`/`vi.doMock`/`vi.unmock`/`vi.doUnmock`/`vi.hoisted`. You can configure this with the `hoistMocks` option when initiating a plugin: |
| 41 | + |
| 42 | +```ts |
| 43 | +import { mockerPlugin } from '@vitest/mocker/node' |
| 44 | + |
| 45 | +export default defineConfig({ |
| 46 | + plugins: [ |
| 47 | + mockerPlugin({ |
| 48 | + hoistMocks: { |
| 49 | + regexpHoistable: /myObj.mock/, |
| 50 | + // you will also need to update other options accordingly |
| 51 | + utilsObjectName: ['myObj'], |
| 52 | + }, |
| 53 | + }), |
| 54 | + ], |
| 55 | +}) |
| 56 | +``` |
| 57 | + |
| 58 | +Now you can call `vi.mock` in your code and the mocker should kick in automatially: |
| 59 | + |
| 60 | +```ts |
| 61 | +import { mocked } from './some-module.js' |
| 62 | + |
| 63 | +vi.mock('./some-module.js', () => { |
| 64 | + return { mocked: true } |
| 65 | +}) |
| 66 | + |
| 67 | +mocked === true |
| 68 | +``` |
| 69 | + |
| 70 | +# Public Exports |
| 71 | + |
| 72 | +## MockerRegistry |
| 73 | + |
| 74 | +Just a cache that holds mocked modules to be used by the actual mocker. |
| 75 | + |
| 76 | +```ts |
| 77 | +import { ManualMockedModule, MockerRegistry } from '@vitest/mocker' |
| 78 | +const registry = new MockerRegistry() |
| 79 | + |
| 80 | +// Vitest requites the original ID for better error messages, |
| 81 | +// You can pass down anything related to the module there |
| 82 | +registry.register('manual', './id.js', '/users/custom/id.js', factory) |
| 83 | +registry.get('/users/custom/id.js') instanceof ManualMockedModule |
| 84 | +``` |
| 85 | + |
| 86 | +## mockObject |
| 87 | + |
| 88 | +Deeply mock an object. This is the function that automocks modules in Vitest. |
| 89 | + |
| 90 | +```ts |
| 91 | +import { mockObject } from '@vitest/mocker' |
| 92 | +import { spyOn } from '@vitest/spy' |
| 93 | + |
| 94 | +mockObject( |
| 95 | + { |
| 96 | + // this is needed because it can be used in vm context |
| 97 | + globalContructors: { |
| 98 | + Object, |
| 99 | + // ... |
| 100 | + }, |
| 101 | + // you can provide your own spyOn implementation |
| 102 | + spyOn, |
| 103 | + mockType: 'automock' // or 'autospy' |
| 104 | + }, |
| 105 | + { |
| 106 | + myDeep: { |
| 107 | + object() { |
| 108 | + return { |
| 109 | + willAlso: { |
| 110 | + beMocked() { |
| 111 | + return true |
| 112 | + }, |
| 113 | + }, |
| 114 | + } |
| 115 | + }, |
| 116 | + }, |
| 117 | + } |
| 118 | +) |
| 119 | +``` |
| 120 | + |
| 121 | +## automockPlugin |
| 122 | + |
| 123 | +The Vite plugin that can mock any module in the browser. |
| 124 | + |
| 125 | +```ts |
| 126 | +import { automockPlugin } from '@vitest/mocker/node' |
| 127 | +import { createServer } from 'vite' |
| 128 | + |
| 129 | +await createServer({ |
| 130 | + plugins: [ |
| 131 | + automockPlugin(), |
| 132 | + ], |
| 133 | +}) |
| 134 | +``` |
| 135 | + |
| 136 | +Any module that has `mock=automock` or `mock=autospy` query will be mocked: |
| 137 | + |
| 138 | +```ts |
| 139 | +import { calculator } from './src/calculator.js?mock=automock' |
| 140 | + |
| 141 | +calculator(1, 2) |
| 142 | +calculator.mock.calls[0] === [1, 2] |
| 143 | +``` |
| 144 | + |
| 145 | +Ideally, you would inject those queries somehow, not write them manually. In the future, this package will support `with { mock: 'auto' }` syntax. |
| 146 | + |
| 147 | +> [!WARNING] |
| 148 | +> The plugin expects a global `__vitest_mocker__` variable with a `mockObject` method. Make sure it is injected _before_ the mocked file is imported. You can also configure the accessor by changing the `globalThisAccessor` option. |
| 149 | +
|
| 150 | +> [!NOTE] |
| 151 | +> This plugin is included in `mockerPlugin`. |
| 152 | +
|
| 153 | +## automockModule |
| 154 | + |
| 155 | +Replace every export with a mock in the code. |
| 156 | + |
| 157 | +```ts |
| 158 | +import { automockModule } from '@vitest/mocker/node' |
| 159 | +import { parseAst } from 'vite' |
| 160 | + |
| 161 | +const ms = await automockModule( |
| 162 | + `export function test() {}`, |
| 163 | + 'automock', |
| 164 | + parseAst, |
| 165 | +) |
| 166 | +console.log( |
| 167 | + ms.toString(), |
| 168 | + ms.generateMap({ hires: 'boundary' }) |
| 169 | +) |
| 170 | +``` |
| 171 | + |
| 172 | +Produces this: |
| 173 | + |
| 174 | +```ts |
| 175 | +function test() {} |
| 176 | + |
| 177 | +const __vitest_es_current_module__ = { |
| 178 | + __esModule: true, |
| 179 | + test, |
| 180 | +} |
| 181 | +const __vitest_mocked_module__ = __vitest_mocker__.mockObject(__vitest_es_current_module__, 'automock') |
| 182 | +const __vitest_mocked_0__ = __vitest_mocked_module__.test |
| 183 | +export { |
| 184 | + __vitest_mocked_0__ as test, |
| 185 | +} |
| 186 | +``` |
| 187 | + |
| 188 | +## hoistMocksPlugin |
| 189 | + |
| 190 | +The plugin that hoists every compiler hint, replaces every static import with dynamic one and updates exports access to make sure live-binding is not broken. |
| 191 | + |
| 192 | +```ts |
| 193 | +import { hoistMocksPlugin } from '@vitest/mocker/node' |
| 194 | +import { createServer } from 'vite' |
| 195 | + |
| 196 | +await createServer({ |
| 197 | + plugins: [ |
| 198 | + hoistMocksPlugin({ |
| 199 | + hoistedModules: ['virtual:my-module'], |
| 200 | + regexpHoistable: /myObj.(mock|hoist)/, |
| 201 | + utilsObjectName: ['myObj'], |
| 202 | + hoistableMockMethodNames: ['mock'], |
| 203 | + // disable support for vi.mock(import('./path')) |
| 204 | + dynamicImportMockMethodNames: [], |
| 205 | + hoistedMethodNames: ['hoist'], |
| 206 | + }), |
| 207 | + ], |
| 208 | +}) |
| 209 | +``` |
| 210 | + |
| 211 | +> [!NOTE] |
| 212 | +> This plugin is included in `mockerPlugin`. |
| 213 | +
|
| 214 | +## hoistMocks |
| 215 | + |
| 216 | +Hoist compiler hints, replace static imports with dynamic ones and update exports access to make sure live-binding is not broken. |
| 217 | + |
| 218 | +This is required to ensure mocks are resolved before we import the user module. |
| 219 | + |
| 220 | +```ts |
| 221 | +import { parseAst } from 'vite' |
| 222 | + |
| 223 | +hoistMocks( |
| 224 | + ` |
| 225 | +import { mocked } from './some-module.js' |
| 226 | +
|
| 227 | +vi.mock('./some-module.js', () => { |
| 228 | + return { mocked: true } |
| 229 | +}) |
| 230 | +
|
| 231 | +mocked === true |
| 232 | + `, |
| 233 | + '/my-module.js', |
| 234 | + parseAst |
| 235 | +) |
| 236 | +``` |
| 237 | + |
| 238 | +Produces this code: |
| 239 | + |
| 240 | +```js |
| 241 | +vi.mock('./some-module.js', () => { |
| 242 | + return { mocked: true } |
| 243 | +}) |
| 244 | + |
| 245 | +const __vi_import_0__ = await import('./some-module.js') |
| 246 | +__vi_import_0__.mocked === true |
| 247 | +``` |
| 248 | + |
| 249 | +## dynamicImportPlugin |
| 250 | + |
| 251 | +Wrap every dynamic import with `mocker.wrapDynamicImport`. This is required to ensure mocks are resolved before we import the user module. You can configure the `globalThis` accessor with `globalThisAccessor` option. |
| 252 | + |
| 253 | +It doesn't make sense to use this plugin in isolation from other plugins. |
| 254 | + |
| 255 | +```ts |
| 256 | +import { dynamicImportPlugin } from '@vitest/mocker/node' |
| 257 | +import { createServer } from 'vite' |
| 258 | + |
| 259 | +await createServer({ |
| 260 | + plugins: [ |
| 261 | + dynamicImportPlugin({ |
| 262 | + globalThisAccessor: 'Symbol.for("my-mocker")' |
| 263 | + }), |
| 264 | + ], |
| 265 | +}) |
| 266 | +``` |
| 267 | + |
| 268 | +```ts |
| 269 | +await import('./my-module.js') |
| 270 | + |
| 271 | +// produces this: |
| 272 | +await globalThis[`Symbol.for('my-mocker')`].wrapDynamicImport(() => import('./my-module.js')) |
| 273 | +``` |
| 274 | + |
| 275 | +## findMockRedirect |
| 276 | + |
| 277 | +This method will try to find a file inside `__mocks__` folder that corresponds to the current file. |
| 278 | + |
| 279 | +```ts |
| 280 | +import { findMockRedirect } from '@vitest/mocker/node' |
| 281 | + |
| 282 | +// uses sync fs APIs |
| 283 | +const mockRedirect = findMockRedirect( |
| 284 | + root, |
| 285 | + 'vscode', |
| 286 | + 'vscode', // if defined, will assume the file is a library name |
| 287 | +) |
| 288 | +// mockRedirect == ${root}/__mocks__/vscode.js |
| 289 | +``` |
0 commit comments