···16161717### vi.mock
18181919-- **Type**: `(path: string, factory?: (importOriginal: () => unknown) => unknown) => void`
2020-- **Type**: `<T>(path: Promise<T>, factory?: (importOriginal: () => T) => T | Promise<T>) => void`
1919+- **Type**: `(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void`
2020+- **Type**: `<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void`
21212222Substitutes 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···2929Vitest 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:::
31313232-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.
3232+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.
33333434Unlike 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.
35353636-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).
3636+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.
3737+3838+```ts
3939+import { calculator } from './src/calculator.ts'
4040+4141+vi.mock('./src/calculator.ts', { spy: true })
4242+4343+// calls the original implementation,
4444+// but allows asserting the behaviour later
4545+const result = calculator(1, 2)
4646+4747+expect(result).toBe(3)
4848+expect(calculator).toHaveBeenCalledWith(1, 2)
4949+expect(calculator).toHaveReturned(3)
5050+```
5151+5252+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).
37533854```ts twoslash
3955// @filename: ./path/to/module.js
···103119```
104120:::
105121106106-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.
122122+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.
107123108124For example, you have this file structure:
109125···118134 - increment.test.js
119135```
120136121121-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:
137137+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:
122138123139```ts
124140// increment.test.js
···144160145161### vi.doMock
146162147147-- **Type**: `(path: string, factory?: (importOriginal: () => unknown) => unknown) => void`
148148-- **Type**: `<T>(path: Promise<T>, factory?: (importOriginal: () => T) => T | Promise<T>) => void`
163163+- **Type**: `(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void`
164164+- **Type**: `<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void`
149165150166The 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···416432spy.mockReturnValue(10)
417433console.log(cart.getApples()) // still 42!
418434```
435435+:::
436436+437437+::: tip
438438+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.
439439+440440+```ts
441441+import { calculator } from './src/calculator.ts'
442442+443443+vi.mock('./src/calculator.ts', { spy: true })
444444+445445+calculator(1, 2)
446446+447447+expect(calculator).toHaveBeenCalledWith(1, 2)
448448+expect(calculator).toHaveReturned(3)
449449+```
450450+451451+And while it is possible to spy on exports in `jsdom` or other Node.js environments, this might change in the future.
419452:::
420453421454### vi.stubEnv {#vi-stubenv}
···11+# Using as a Vite plugin
22+33+Make sure you have `vite` and `@vitest/spy` installed (and `msw` if you are planning to use `ModuleMockerMSWInterceptor`).
44+55+```ts
66+import { mockerPlugin } from '@vitest/mocker/node'
77+88+export default defineConfig({
99+ plugins: [mockerPlugin()],
1010+})
1111+```
1212+1313+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.
1414+1515+```ts
1616+import {
1717+ ModuleMockerMSWInterceptor,
1818+ ModuleMockerServerInterceptor,
1919+ registerModuleMocker
2020+} from '@vitest/mocker/register'
2121+2222+// you can use either a server interceptor (relies on Vite's websocket connection)
2323+const vi = registerModuleMocker(() => new ModuleMockerServerInterceptor())
2424+// or you can use MSW to intercept requests directly in the browser
2525+const vi = registerModuleMocker(globalThisAccessor => new ModuleMockerMSWInterceptor({ globalThisAccessor }))
2626+```
2727+2828+```ts
2929+// you can also just import "auto-register" at the top of your entry point,
3030+// this will use the server interceptor by default
3131+import '@vitest/mocker/auto-register'
3232+// if you do this, you can create compiler hints with "createCompilerHints"
3333+// utility to use in your own code
3434+import { createCompilerHints } from '@vitest/mocker/browser'
3535+const vi = createCompilerHints()
3636+```
3737+3838+`registerModuleMocker` returns compiler hints that Vite plugin will look for.
3939+4040+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:
4141+4242+```ts
4343+import { mockerPlugin } from '@vitest/mocker/node'
4444+4545+export default defineConfig({
4646+ plugins: [
4747+ mockerPlugin({
4848+ hoistMocks: {
4949+ regexpHoistable: /myObj.mock/,
5050+ // you will also need to update other options accordingly
5151+ utilsObjectName: ['myObj'],
5252+ },
5353+ }),
5454+ ],
5555+})
5656+```
5757+5858+Now you can call `vi.mock` in your code and the mocker should kick in automatially:
5959+6060+```ts
6161+import { mocked } from './some-module.js'
6262+6363+vi.mock('./some-module.js', () => {
6464+ return { mocked: true }
6565+})
6666+6767+mocked === true
6868+```
6969+7070+# Public Exports
7171+7272+## MockerRegistry
7373+7474+Just a cache that holds mocked modules to be used by the actual mocker.
7575+7676+```ts
7777+import { ManualMockedModule, MockerRegistry } from '@vitest/mocker'
7878+const registry = new MockerRegistry()
7979+8080+// Vitest requites the original ID for better error messages,
8181+// You can pass down anything related to the module there
8282+registry.register('manual', './id.js', '/users/custom/id.js', factory)
8383+registry.get('/users/custom/id.js') instanceof ManualMockedModule
8484+```
8585+8686+## mockObject
8787+8888+Deeply mock an object. This is the function that automocks modules in Vitest.
8989+9090+```ts
9191+import { mockObject } from '@vitest/mocker'
9292+import { spyOn } from '@vitest/spy'
9393+9494+mockObject(
9595+ {
9696+ // this is needed because it can be used in vm context
9797+ globalContructors: {
9898+ Object,
9999+ // ...
100100+ },
101101+ // you can provide your own spyOn implementation
102102+ spyOn,
103103+ mockType: 'automock' // or 'autospy'
104104+ },
105105+ {
106106+ myDeep: {
107107+ object() {
108108+ return {
109109+ willAlso: {
110110+ beMocked() {
111111+ return true
112112+ },
113113+ },
114114+ }
115115+ },
116116+ },
117117+ }
118118+)
119119+```
120120+121121+## automockPlugin
122122+123123+The Vite plugin that can mock any module in the browser.
124124+125125+```ts
126126+import { automockPlugin } from '@vitest/mocker/node'
127127+import { createServer } from 'vite'
128128+129129+await createServer({
130130+ plugins: [
131131+ automockPlugin(),
132132+ ],
133133+})
134134+```
135135+136136+Any module that has `mock=automock` or `mock=autospy` query will be mocked:
137137+138138+```ts
139139+import { calculator } from './src/calculator.js?mock=automock'
140140+141141+calculator(1, 2)
142142+calculator.mock.calls[0] === [1, 2]
143143+```
144144+145145+Ideally, you would inject those queries somehow, not write them manually. In the future, this package will support `with { mock: 'auto' }` syntax.
146146+147147+> [!WARNING]
148148+> 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.
149149+150150+> [!NOTE]
151151+> This plugin is included in `mockerPlugin`.
152152+153153+## automockModule
154154+155155+Replace every export with a mock in the code.
156156+157157+```ts
158158+import { automockModule } from '@vitest/mocker/node'
159159+import { parseAst } from 'vite'
160160+161161+const ms = await automockModule(
162162+ `export function test() {}`,
163163+ 'automock',
164164+ parseAst,
165165+)
166166+console.log(
167167+ ms.toString(),
168168+ ms.generateMap({ hires: 'boundary' })
169169+)
170170+```
171171+172172+Produces this:
173173+174174+```ts
175175+function test() {}
176176+177177+const __vitest_es_current_module__ = {
178178+ __esModule: true,
179179+ test,
180180+}
181181+const __vitest_mocked_module__ = __vitest_mocker__.mockObject(__vitest_es_current_module__, 'automock')
182182+const __vitest_mocked_0__ = __vitest_mocked_module__.test
183183+export {
184184+ __vitest_mocked_0__ as test,
185185+}
186186+```
187187+188188+## hoistMocksPlugin
189189+190190+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.
191191+192192+```ts
193193+import { hoistMocksPlugin } from '@vitest/mocker/node'
194194+import { createServer } from 'vite'
195195+196196+await createServer({
197197+ plugins: [
198198+ hoistMocksPlugin({
199199+ hoistedModules: ['virtual:my-module'],
200200+ regexpHoistable: /myObj.(mock|hoist)/,
201201+ utilsObjectName: ['myObj'],
202202+ hoistableMockMethodNames: ['mock'],
203203+ // disable support for vi.mock(import('./path'))
204204+ dynamicImportMockMethodNames: [],
205205+ hoistedMethodNames: ['hoist'],
206206+ }),
207207+ ],
208208+})
209209+```
210210+211211+> [!NOTE]
212212+> This plugin is included in `mockerPlugin`.
213213+214214+## hoistMocks
215215+216216+Hoist compiler hints, replace static imports with dynamic ones and update exports access to make sure live-binding is not broken.
217217+218218+This is required to ensure mocks are resolved before we import the user module.
219219+220220+```ts
221221+import { parseAst } from 'vite'
222222+223223+hoistMocks(
224224+ `
225225+import { mocked } from './some-module.js'
226226+227227+vi.mock('./some-module.js', () => {
228228+ return { mocked: true }
229229+})
230230+231231+mocked === true
232232+ `,
233233+ '/my-module.js',
234234+ parseAst
235235+)
236236+```
237237+238238+Produces this code:
239239+240240+```js
241241+vi.mock('./some-module.js', () => {
242242+ return { mocked: true }
243243+})
244244+245245+const __vi_import_0__ = await import('./some-module.js')
246246+__vi_import_0__.mocked === true
247247+```
248248+249249+## dynamicImportPlugin
250250+251251+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.
252252+253253+It doesn't make sense to use this plugin in isolation from other plugins.
254254+255255+```ts
256256+import { dynamicImportPlugin } from '@vitest/mocker/node'
257257+import { createServer } from 'vite'
258258+259259+await createServer({
260260+ plugins: [
261261+ dynamicImportPlugin({
262262+ globalThisAccessor: 'Symbol.for("my-mocker")'
263263+ }),
264264+ ],
265265+})
266266+```
267267+268268+```ts
269269+await import('./my-module.js')
270270+271271+// produces this:
272272+await globalThis[`Symbol.for('my-mocker')`].wrapDynamicImport(() => import('./my-module.js'))
273273+```
274274+275275+## findMockRedirect
276276+277277+This method will try to find a file inside `__mocks__` folder that corresponds to the current file.
278278+279279+```ts
280280+import { findMockRedirect } from '@vitest/mocker/node'
281281+282282+// uses sync fs APIs
283283+const mockRedirect = findMockRedirect(
284284+ root,
285285+ 'vscode',
286286+ 'vscode', // if defined, will assume the file is a library name
287287+)
288288+// mockRedirect == ${root}/__mocks__/vscode.js
289289+```
···11+import type { MockedModuleType } from './registry'
22+33+type Key = string | symbol
44+55+export interface MockObjectOptions {
66+ type: MockedModuleType
77+ globalConstructors: GlobalConstructors
88+ spyOn: (obj: any, prop: Key) => any
99+}
1010+1111+export function mockObject(
1212+ options: MockObjectOptions,
1313+ object: Record<Key, any>,
1414+ mockExports: Record<Key, any> = {},
1515+): Record<Key, any> {
1616+ const finalizers = new Array<() => void>()
1717+ const refs = new RefTracker()
1818+1919+ const define = (container: Record<Key, any>, key: Key, value: any) => {
2020+ try {
2121+ container[key] = value
2222+ return true
2323+ }
2424+ catch {
2525+ return false
2626+ }
2727+ }
2828+2929+ const mockPropertiesOf = (
3030+ container: Record<Key, any>,
3131+ newContainer: Record<Key, any>,
3232+ ) => {
3333+ const containerType = getType(container)
3434+ const isModule = containerType === 'Module' || !!container.__esModule
3535+ for (const { key: property, descriptor } of getAllMockableProperties(
3636+ container,
3737+ isModule,
3838+ options.globalConstructors,
3939+ )) {
4040+ // Modules define their exports as getters. We want to process those.
4141+ if (!isModule && descriptor.get) {
4242+ try {
4343+ Object.defineProperty(newContainer, property, descriptor)
4444+ }
4545+ catch {
4646+ // Ignore errors, just move on to the next prop.
4747+ }
4848+ continue
4949+ }
5050+5151+ // Skip special read-only props, we don't want to mess with those.
5252+ if (isSpecialProp(property, containerType)) {
5353+ continue
5454+ }
5555+5656+ const value = container[property]
5757+5858+ // Special handling of references we've seen before to prevent infinite
5959+ // recursion in circular objects.
6060+ const refId = refs.getId(value)
6161+ if (refId !== undefined) {
6262+ finalizers.push(() =>
6363+ define(newContainer, property, refs.getMockedValue(refId)),
6464+ )
6565+ continue
6666+ }
6767+6868+ const type = getType(value)
6969+7070+ if (Array.isArray(value)) {
7171+ define(newContainer, property, [])
7272+ continue
7373+ }
7474+7575+ const isFunction
7676+ = type.includes('Function') && typeof value === 'function'
7777+ if (
7878+ (!isFunction || value.__isMockFunction)
7979+ && type !== 'Object'
8080+ && type !== 'Module'
8181+ ) {
8282+ define(newContainer, property, value)
8383+ continue
8484+ }
8585+8686+ // Sometimes this assignment fails for some unknown reason. If it does,
8787+ // just move along.
8888+ if (!define(newContainer, property, isFunction ? value : {})) {
8989+ continue
9090+ }
9191+9292+ if (isFunction) {
9393+ if (!options.spyOn) {
9494+ throw new Error(
9595+ '[@vitest/mocker] `spyOn` is not defined. This is a Vitest error. Please open a new issue with reproduction.',
9696+ )
9797+ }
9898+ const spyOn = options.spyOn
9999+ function mockFunction(this: any) {
100100+ // detect constructor call and mock each instance's methods
101101+ // so that mock states between prototype/instances don't affect each other
102102+ // (jest reference https://github.com/jestjs/jest/blob/2c3d2409879952157433de215ae0eee5188a4384/packages/jest-mock/src/index.ts#L678-L691)
103103+ if (this instanceof newContainer[property]) {
104104+ for (const { key, descriptor } of getAllMockableProperties(
105105+ this,
106106+ false,
107107+ options.globalConstructors,
108108+ )) {
109109+ // skip getter since it's not mocked on prototype as well
110110+ if (descriptor.get) {
111111+ continue
112112+ }
113113+114114+ const value = this[key]
115115+ const type = getType(value)
116116+ const isFunction
117117+ = type.includes('Function') && typeof value === 'function'
118118+ if (isFunction) {
119119+ // mock and delegate calls to original prototype method, which should be also mocked already
120120+ const original = this[key]
121121+ const mock = spyOn(this, key as string)
122122+ .mockImplementation(original)
123123+ mock.mockRestore = () => {
124124+ mock.mockReset()
125125+ mock.mockImplementation(original)
126126+ return mock
127127+ }
128128+ }
129129+ }
130130+ }
131131+ }
132132+ const mock = spyOn(newContainer, property)
133133+ if (options.type === 'automock') {
134134+ mock.mockImplementation(mockFunction)
135135+ mock.mockRestore = () => {
136136+ mock.mockReset()
137137+ mock.mockImplementation(mockFunction)
138138+ return mock
139139+ }
140140+ }
141141+ // tinyspy retains length, but jest doesn't.
142142+ Object.defineProperty(newContainer[property], 'length', { value: 0 })
143143+ }
144144+145145+ refs.track(value, newContainer[property])
146146+ mockPropertiesOf(value, newContainer[property])
147147+ }
148148+ }
149149+150150+ const mockedObject: Record<Key, any> = mockExports
151151+ mockPropertiesOf(object, mockedObject)
152152+153153+ // Plug together refs
154154+ for (const finalizer of finalizers) {
155155+ finalizer()
156156+ }
157157+158158+ return mockedObject
159159+}
160160+161161+class RefTracker {
162162+ private idMap = new Map<any, number>()
163163+ private mockedValueMap = new Map<number, any>()
164164+165165+ public getId(value: any) {
166166+ return this.idMap.get(value)
167167+ }
168168+169169+ public getMockedValue(id: number) {
170170+ return this.mockedValueMap.get(id)
171171+ }
172172+173173+ public track(originalValue: any, mockedValue: any): number {
174174+ const newId = this.idMap.size
175175+ this.idMap.set(originalValue, newId)
176176+ this.mockedValueMap.set(newId, mockedValue)
177177+ return newId
178178+ }
179179+}
180180+181181+function getType(value: unknown): string {
182182+ return Object.prototype.toString.apply(value).slice(8, -1)
183183+}
184184+185185+function isSpecialProp(prop: Key, parentType: string) {
186186+ return (
187187+ parentType.includes('Function')
188188+ && typeof prop === 'string'
189189+ && ['arguments', 'callee', 'caller', 'length', 'name'].includes(prop)
190190+ )
191191+}
192192+193193+export interface GlobalConstructors {
194194+ Object: ObjectConstructor
195195+ Function: FunctionConstructor
196196+ RegExp: RegExpConstructor
197197+ Array: ArrayConstructor
198198+ Map: MapConstructor
199199+}
200200+201201+function getAllMockableProperties(
202202+ obj: any,
203203+ isModule: boolean,
204204+ constructors: GlobalConstructors,
205205+) {
206206+ const { Map, Object, Function, RegExp, Array } = constructors
207207+208208+ const allProps = new Map<
209209+ string | symbol,
210210+ { key: string | symbol; descriptor: PropertyDescriptor }
211211+ >()
212212+ let curr = obj
213213+ do {
214214+ // we don't need properties from these
215215+ if (
216216+ curr === Object.prototype
217217+ || curr === Function.prototype
218218+ || curr === RegExp.prototype
219219+ ) {
220220+ break
221221+ }
222222+223223+ collectOwnProperties(curr, (key) => {
224224+ const descriptor = Object.getOwnPropertyDescriptor(curr, key)
225225+ if (descriptor) {
226226+ allProps.set(key, { key, descriptor })
227227+ }
228228+ })
229229+ // eslint-disable-next-line no-cond-assign
230230+ } while ((curr = Object.getPrototypeOf(curr)))
231231+ // default is not specified in ownKeys, if module is interoped
232232+ if (isModule && !allProps.has('default') && 'default' in obj) {
233233+ const descriptor = Object.getOwnPropertyDescriptor(obj, 'default')
234234+ if (descriptor) {
235235+ allProps.set('default', { key: 'default', descriptor })
236236+ }
237237+ }
238238+ return Array.from(allProps.values())
239239+}
240240+241241+function collectOwnProperties(
242242+ obj: any,
243243+ collector: Set<string | symbol> | ((key: string | symbol) => void),
244244+) {
245245+ const collect
246246+ = typeof collector === 'function'
247247+ ? collector
248248+ : (key: string | symbol) => collector.add(key)
249249+ Object.getOwnPropertyNames(obj).forEach(collect)
250250+ Object.getOwnPropertySymbols(obj).forEach(collect)
251251+}
+6
packages/mocker/src/browser/auto-register.ts
···11+import { ModuleMockerServerInterceptor } from './interceptor-native'
22+import { registerModuleMocker } from './register'
33+44+registerModuleMocker(
55+ () => new ModuleMockerServerInterceptor(),
66+)
+146
packages/mocker/src/browser/hints.ts
···11+import type { MaybeMockedDeep } from '@vitest/spy'
22+import { createSimpleStackTrace } from '@vitest/utils'
33+import { parseSingleStack } from '@vitest/utils/source-map'
44+import type { ModuleMockFactoryWithHelper, ModuleMockOptions } from '../types'
55+import type { ModuleMocker } from './mocker'
66+77+export interface CompilerHintsOptions {
88+ /**
99+ * This is the key used to access the globalThis object in the worker.
1010+ * Unlike `globalThisAccessor` in other APIs, this is not injected into the script.
1111+ * ```ts
1212+ * // globalThisKey: '__my_variable__' produces:
1313+ * globalThis['__my_variable__']
1414+ * // globalThisKey: '"__my_variable__"' produces:
1515+ * globalThis['"__my_variable__"'] // notice double quotes
1616+ * ```
1717+ * @default '__vitest_mocker__'
1818+ */
1919+ globalThisKey?: string
2020+}
2121+2222+export interface ModuleMockerCompilerHints {
2323+ hoisted: <T>(factory: () => T) => T
2424+ mock: (path: string | Promise<unknown>, factory?: ModuleMockOptions | ModuleMockFactoryWithHelper) => void
2525+ unmock: (path: string | Promise<unknown>) => void
2626+ doMock: (path: string | Promise<unknown>, factory?: ModuleMockOptions | ModuleMockFactoryWithHelper) => void
2727+ doUnmock: (path: string | Promise<unknown>) => void
2828+ importActual: <T>(path: string) => Promise<T>
2929+ importMock: <T>(path: string) => Promise<MaybeMockedDeep<T>>
3030+}
3131+3232+export function createCompilerHints(options?: CompilerHintsOptions): ModuleMockerCompilerHints {
3333+ const globalThisAccessor = options?.globalThisKey || '__vitest_mocker__'
3434+ function _mocker(): ModuleMocker {
3535+ // @ts-expect-error injected by the plugin
3636+ return typeof globalThis[globalThisAccessor] !== 'undefined'
3737+ // @ts-expect-error injected by the plugin
3838+ ? globalThis[globalThisAccessor]
3939+ : new Proxy(
4040+ {},
4141+ {
4242+ get(_, name) {
4343+ throw new Error(
4444+ 'Vitest mocker was not initialized in this environment. '
4545+ + `vi.${String(name)}() is forbidden.`,
4646+ )
4747+ },
4848+ },
4949+ )
5050+ }
5151+5252+ const getImporter = (name: string) => {
5353+ const stackTrace = /* @__PURE__ */ createSimpleStackTrace({ stackTraceLimit: 5 })
5454+ const stackArray = stackTrace.split('\n')
5555+ // if there is no message in a stack trace, use the item - 1
5656+ const importerStackIndex = stackArray.findIndex((stack) => {
5757+ return stack.includes(` at Object.${name}`) || stack.includes(`${name}@`)
5858+ })
5959+ const stack = /* @__PURE__ */ parseSingleStack(stackArray[importerStackIndex + 1])
6060+ return stack?.file || ''
6161+ }
6262+6363+ return {
6464+ hoisted<T>(factory: () => T): T {
6565+ if (typeof factory !== 'function') {
6666+ throw new TypeError(
6767+ `vi.hoisted() expects a function, but received a ${typeof factory}`,
6868+ )
6969+ }
7070+ return factory()
7171+ },
7272+7373+ mock(path: string | Promise<unknown>, factory?: ModuleMockOptions | ModuleMockFactoryWithHelper): void {
7474+ if (typeof path !== 'string') {
7575+ throw new TypeError(
7676+ `vi.mock() expects a string path, but received a ${typeof path}`,
7777+ )
7878+ }
7979+ const importer = getImporter('mock')
8080+ _mocker().queueMock(
8181+ path,
8282+ importer,
8383+ typeof factory === 'function'
8484+ ? () =>
8585+ factory(() =>
8686+ _mocker().importActual(
8787+ path,
8888+ importer,
8989+ ),
9090+ )
9191+ : factory,
9292+ )
9393+ },
9494+9595+ unmock(path: string | Promise<unknown>): void {
9696+ if (typeof path !== 'string') {
9797+ throw new TypeError(
9898+ `vi.unmock() expects a string path, but received a ${typeof path}`,
9999+ )
100100+ }
101101+ _mocker().queueUnmock(path, getImporter('unmock'))
102102+ },
103103+104104+ doMock(path: string | Promise<unknown>, factory?: ModuleMockOptions | ModuleMockFactoryWithHelper): void {
105105+ if (typeof path !== 'string') {
106106+ throw new TypeError(
107107+ `vi.doMock() expects a string path, but received a ${typeof path}`,
108108+ )
109109+ }
110110+ const importer = getImporter('doMock')
111111+ _mocker().queueMock(
112112+ path,
113113+ importer,
114114+ typeof factory === 'function'
115115+ ? () =>
116116+ factory(() =>
117117+ _mocker().importActual(
118118+ path,
119119+ importer,
120120+ ),
121121+ )
122122+ : factory,
123123+ )
124124+ },
125125+126126+ doUnmock(path: string | Promise<unknown>): void {
127127+ if (typeof path !== 'string') {
128128+ throw new TypeError(
129129+ `vi.doUnmock() expects a string path, but received a ${typeof path}`,
130130+ )
131131+ }
132132+ _mocker().queueUnmock(path, getImporter('doUnmock'))
133133+ },
134134+135135+ async importActual<T = unknown>(path: string): Promise<T> {
136136+ return _mocker().importActual<T>(
137137+ path,
138138+ getImporter('importActual'),
139139+ )
140140+ },
141141+142142+ async importMock<T>(path: string): Promise<MaybeMockedDeep<T>> {
143143+ return _mocker().importMock(path, getImporter('importMock'))
144144+ },
145145+ }
146146+}
+13
packages/mocker/src/browser/index.ts
···11+export type { ModuleMockerInterceptor } from './interceptor'
22+export { ModuleMocker } from './mocker'
33+export { ModuleMockerMSWInterceptor, type ModuleMockerMSWInterceptorOptions } from './interceptor-msw'
44+export { ModuleMockerServerInterceptor } from './interceptor-native'
55+66+export type {
77+ ModuleMockerRPC,
88+ ModuleMockerConfig,
99+ ResolveIdResult,
1010+ ResolveMockResul,
1111+} from './mocker'
1212+export { createCompilerHints } from './hints'
1313+export type { CompilerHintsOptions, ModuleMockerCompilerHints } from './hints'
+182
packages/mocker/src/browser/interceptor-msw.ts
···11+import type { SetupWorker, StartOptions } from 'msw/browser'
22+import type { HttpHandler } from 'msw'
33+import type { ManualMockedModule, MockedModule } from '../registry'
44+import { MockerRegistry } from '../registry'
55+import { cleanUrl } from '../utils'
66+import type { ModuleMockerInterceptor } from './interceptor'
77+88+export interface ModuleMockerMSWInterceptorOptions {
99+ /**
1010+ * The identifier to access the globalThis object in the worker.
1111+ * This will be injected into the script as is, so make sure it's a valid JS expression.
1212+ * @example
1313+ * ```js
1414+ * // globalThisAccessor: '__my_variable__' produces:
1515+ * globalThis[__my_variable__]
1616+ * // globalThisAccessor: 'Symbol.for('secret:mocks')' produces:
1717+ * globalThis[Symbol.for('secret:mocks')]
1818+ * // globalThisAccessor: '"__vitest_mocker__"' (notice quotes) produces:
1919+ * globalThis["__vitest_mocker__"]
2020+ * ```
2121+ * @default `"__vitest_mocker__"`
2222+ */
2323+ globalThisAccessor?: string
2424+ /**
2525+ * Options passed down to `msw.setupWorker().start(options)`
2626+ */
2727+ mswOptions?: StartOptions
2828+ /**
2929+ * A pre-configured `msw.setupWorker` instance.
3030+ */
3131+ mswWorker?: SetupWorker
3232+}
3333+3434+export class ModuleMockerMSWInterceptor implements ModuleMockerInterceptor {
3535+ protected readonly mocks: MockerRegistry = new MockerRegistry()
3636+3737+ private started = false
3838+ private startPromise: undefined | Promise<unknown>
3939+4040+ constructor(
4141+ private readonly options: ModuleMockerMSWInterceptorOptions = {},
4242+ ) {
4343+ if (!options.globalThisAccessor) {
4444+ options.globalThisAccessor = '"__vitest_mocker__"'
4545+ }
4646+ }
4747+4848+ async register(module: MockedModule): Promise<void> {
4949+ await this.init()
5050+ this.mocks.add(module)
5151+ }
5252+5353+ async delete(url: string): Promise<void> {
5454+ await this.init()
5555+ this.mocks.delete(url)
5656+ }
5757+5858+ invalidate(): void {
5959+ this.mocks.clear()
6060+ }
6161+6262+ private async resolveManualMock(mock: ManualMockedModule) {
6363+ const exports = Object.keys(await mock.resolve())
6464+ const module = `const module = globalThis[${this.options.globalThisAccessor!}].getFactoryModule("${mock.url}");`
6565+ const keys = exports
6666+ .map((name) => {
6767+ if (name === 'default') {
6868+ return `export default module["default"];`
6969+ }
7070+ return `export const ${name} = module["${name}"];`
7171+ })
7272+ .join('\n')
7373+ const text = `${module}\n${keys}`
7474+ return new Response(text, {
7575+ headers: {
7676+ 'Content-Type': 'application/javascript',
7777+ },
7878+ })
7979+ }
8080+8181+ protected async init(): Promise<unknown> {
8282+ if (this.started) {
8383+ return
8484+ }
8585+ if (this.startPromise) {
8686+ return this.startPromise
8787+ }
8888+ const worker = this.options.mswWorker
8989+ this.startPromise = Promise.all([
9090+ worker
9191+ ? {
9292+ setupWorker(handler: HttpHandler) {
9393+ worker.use(handler)
9494+ return worker
9595+ },
9696+ }
9797+ : import('msw/browser'),
9898+ import('msw/core/http'),
9999+ ]).then(([{ setupWorker }, { http }]) => {
100100+ const worker = setupWorker(
101101+ http.get(/.+/, async ({ request }) => {
102102+ const path = cleanQuery(request.url.slice(location.origin.length))
103103+ if (!this.mocks.has(path)) {
104104+ // do not cache deps like Vite does for performance
105105+ // because we want to be able to update mocks without restarting the server
106106+ // TODO: check if it's still neded - we invalidate modules after each test
107107+ if (path.includes('/deps/')) {
108108+ return fetch(bypass(request))
109109+ }
110110+111111+ return passthrough()
112112+ }
113113+114114+ const mock = this.mocks.get(path)!
115115+116116+ switch (mock.type) {
117117+ case 'manual':
118118+ return this.resolveManualMock(mock)
119119+ case 'automock':
120120+ case 'autospy':
121121+ return Response.redirect(injectQuery(path, `mock=${mock.type}`))
122122+ case 'redirect':
123123+ return Response.redirect(mock.redirect)
124124+ default:
125125+ throw new Error(`Unknown mock type: ${(mock as any).type}`)
126126+ }
127127+ }),
128128+ )
129129+ return worker.start(this.options.mswOptions)
130130+ })
131131+ .finally(() => {
132132+ this.started = true
133133+ this.startPromise = undefined
134134+ })
135135+ await this.startPromise
136136+ }
137137+}
138138+139139+const timestampRegexp = /(\?|&)t=\d{13}/
140140+const versionRegexp = /(\?|&)v=\w{8}/
141141+function cleanQuery(url: string) {
142142+ return url.replace(timestampRegexp, '').replace(versionRegexp, '')
143143+}
144144+145145+function passthrough() {
146146+ return new Response(null, {
147147+ status: 302,
148148+ statusText: 'Passthrough',
149149+ headers: {
150150+ 'x-msw-intention': 'passthrough',
151151+ },
152152+ })
153153+}
154154+155155+function bypass(request: Request) {
156156+ const clonedRequest = request.clone()
157157+ clonedRequest.headers.set('x-msw-intention', 'bypass')
158158+ const cacheControl = clonedRequest.headers.get('cache-control')
159159+ if (cacheControl) {
160160+ clonedRequest.headers.set(
161161+ 'cache-control',
162162+ // allow reinvalidation of the cache so mocks can be updated
163163+ cacheControl.replace(', immutable', ''),
164164+ )
165165+ }
166166+ return clonedRequest
167167+}
168168+169169+const replacePercentageRE = /%/g
170170+function injectQuery(url: string, queryToInject: string): string {
171171+ // encode percents for consistent behavior with pathToFileURL
172172+ // see #2614 for details
173173+ const resolvedUrl = new URL(
174174+ url.replace(replacePercentageRE, '%25'),
175175+ location.href,
176176+ )
177177+ const { search, hash } = resolvedUrl
178178+ const pathname = cleanUrl(url)
179179+ return `${pathname}?${queryToInject}${search ? `&${search.slice(1)}` : ''}${
180180+ hash ?? ''
181181+ }`
182182+}
+17
packages/mocker/src/browser/interceptor-native.ts
···11+import type { MockedModule } from '../registry'
22+import type { ModuleMockerInterceptor } from './interceptor'
33+import { rpc } from './utils'
44+55+export class ModuleMockerServerInterceptor implements ModuleMockerInterceptor {
66+ async register(module: MockedModule): Promise<void> {
77+ await rpc('vitest:interceptor:register', module.toJSON())
88+ }
99+1010+ async delete(id: string): Promise<void> {
1111+ await rpc('vitest:interceptor:delete', id)
1212+ }
1313+1414+ invalidate(): void {
1515+ rpc('vitest:interceptor:invalidate')
1616+ }
1717+}
···99 Node as _Node,
1010} from 'estree'
1111import { walk as eswalk } from 'estree-walker'
1212+import type { Rollup } from 'vite'
12131314export type * from 'estree'
1415···5960 * Except this is using acorn AST
6061 */
6162export function esmWalker(
6262- root: Node,
6363+ root: Rollup.ProgramNode,
6364 { onIdentifier, onImportMeta, onDynamicImport, onCallExpression }: Visitors,
6465): void {
6566 const parentStack: Node[] = []
···115116 }
116117 }
117118118118- eswalk(root, {
119119+ eswalk(root as Node, {
119120 enter(node, parent) {
120121 if (node.type === 'ImportDeclaration') {
121122 return this.skip()
+29
packages/vitest/LICENSE.md
···1557155715581558---------------------------------------
1559155915601560+## tinyexec
15611561+License: MIT
15621562+By: James Garbutt
15631563+Repository: git+https://github.com/tinylibs/tinyexec.git
15641564+15651565+> MIT License
15661566+>
15671567+> Copyright (c) 2024 Tinylibs
15681568+>
15691569+> Permission is hereby granted, free of charge, to any person obtaining a copy
15701570+> of this software and associated documentation files (the "Software"), to deal
15711571+> in the Software without restriction, including without limitation the rights
15721572+> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15731573+> copies of the Software, and to permit persons to whom the Software is
15741574+> furnished to do so, subject to the following conditions:
15751575+>
15761576+> The above copyright notice and this permission notice shall be included in all
15771577+> copies or substantial portions of the Software.
15781578+>
15791579+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15801580+> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15811581+> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15821582+> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15831583+> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15841584+> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
15851585+> SOFTWARE.
15861586+15871587+---------------------------------------
15881588+15601589## to-regex-range
15611590License: MIT
15621591By: Jon Schlinkert, Rouven Weßling
···33import { ModuleCacheMap } from 'vite-node/client'
44import { loadEnvironment } from '../integrations/env/loader'
55import { isChildProcess, setProcessTitle } from '../utils/base'
66-import type { ContextRPC } from '../types/worker'
66+import type { ContextRPC, WorkerGlobalState } from '../types/worker'
77import { setupInspect } from './inspector'
88import { createRuntimeRpc, rpcDone } from './rpc'
99import type { VitestWorker } from './workers/types'
···6363 ctx,
6464 // here we create a new one, workers can reassign this if they need to keep it non-isolated
6565 moduleCache: new ModuleCacheMap(),
6666- mockMap: new Map(),
6766 config: ctx.config,
6867 onCancel,
6968 environment,
···7372 },
7473 rpc,
7574 providedContext: ctx.providedContext,
7676- }
7575+ } satisfies WorkerGlobalState
77767877 const methodName = mehtod === 'collect' ? 'collectTests' : 'runTests'
7978
-3
packages/vitest/src/runtime/workers/base.ts
···33import { provideWorkerState } from '../utils'
44import type { ContextExecutorOptions, VitestExecutor } from '../execute'
55import { getDefaultRequestStubs, startVitestExecutor } from '../execute'
66-import type { MockMap } from '../../types/mocker'
7687let _viteNode: VitestExecutor
98109const moduleCache = new ModuleCacheMap()
1111-const mockMap: MockMap = new Map()
12101311async function startViteNode(options: ContextExecutorOptions) {
1412 if (_viteNode) {
···2321 const { ctx } = state
2422 // state has new context, but we want to reuse existing ones
2523 state.moduleCache = moduleCache
2626- state.mockMap = mockMap
27242825 provideWorkerState(globalThis, state)
2926