-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: check support for custom manual mocks
- Loading branch information
1 parent
3d8c345
commit fac977c
Showing
16 changed files
with
938 additions
and
350 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,289 @@ | ||
# Using as a Vite plugin | ||
|
||
Make sure you have `vite` and `@vitest/spy` installed (and `msw` if you are planning to use `ModuleMockerMSWInterceptor`). | ||
|
||
```ts | ||
import { mockerPlugin } from '@vitest/mocker/node' | ||
|
||
export default defineConfig({ | ||
plugins: [mockerPlugin()], | ||
}) | ||
``` | ||
|
||
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. | ||
|
||
```ts | ||
import { | ||
ModuleMockerMSWInterceptor, | ||
ModuleMockerServerInterceptor, | ||
registerModuleMocker | ||
} from '@vitest/mocker/register' | ||
|
||
// you can use either a server interceptor (relies on Vite's websocket connection) | ||
const vi = registerModuleMocker(new ModuleMockerServerInterceptor()) | ||
// or you can use MSW to intercept requests directly in the browser | ||
const vi = registerModuleMocker(new ModuleMockerMSWInterceptor()) | ||
``` | ||
|
||
```ts | ||
// you can also just import "auto-register" at the top of your entry point, | ||
// this will use the server interceptor by default | ||
import '@vitest/mocker/auto-register' | ||
// if you do this, you can create compiler hints with "createCompilerHints" | ||
// utility to use in your own code | ||
import { createCompilerHints } from '@vitest/mocker/browser' | ||
const vi = createCompilerHints() | ||
``` | ||
|
||
`registerModuleMocker` returns compiler hints that Vite plugin will look for. | ||
|
||
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: | ||
|
||
```ts | ||
import { mockerPlugin } from '@vitest/mocker/node' | ||
|
||
export default defineConfig({ | ||
plugins: [ | ||
mockerPlugin({ | ||
hoistMocks: { | ||
regexpHoistable: /myObj.mock/, | ||
// you will also need to update other options accordingly | ||
utilsObjectName: ['myObj'], | ||
}, | ||
}), | ||
], | ||
}) | ||
``` | ||
|
||
Now you can call `vi.mock` in your code and the mocker should kick in automatially: | ||
|
||
```ts | ||
import { mocked } from './some-module.js' | ||
|
||
vi.mock('./some-module.js', () => { | ||
return { mocked: true } | ||
}) | ||
|
||
mocked === true | ||
``` | ||
|
||
# Public Exports | ||
|
||
## MockerRegistry | ||
|
||
Just a cache that holds mocked modules to be used by the actual mocker. | ||
|
||
```ts | ||
import { ManualMockedModule, MockerRegistry } from '@vitest/mocker' | ||
const registry = new MockerRegistry() | ||
|
||
// Vitest requites the original ID for better error messages, | ||
// You can pass down anything related to the module there | ||
registry.register('manual', './id.js', '/users/custom/id.js', factory) | ||
registry.get('/users/custom/id.js') instanceof ManualMockedModule | ||
``` | ||
|
||
## mockObject | ||
|
||
Deeply mock an object. This is the function that automocks modules in Vitest. | ||
|
||
```ts | ||
import { mockObject } from '@vitest/mocker' | ||
import { spyOn } from '@vitest/spy' | ||
|
||
mockObject( | ||
{ | ||
// this is needed because it can be used in vm context | ||
globalContructors: { | ||
Object, | ||
// ... | ||
}, | ||
// you can provide your own spyOn implementation | ||
spyOn, | ||
mockType: 'automock' // or 'autospy' | ||
}, | ||
{ | ||
myDeep: { | ||
object() { | ||
return { | ||
willAlso: { | ||
beMocked() { | ||
return true | ||
}, | ||
}, | ||
} | ||
}, | ||
}, | ||
} | ||
) | ||
``` | ||
|
||
## automockPlugin | ||
|
||
The Vite plugin that can mock any module in the browser. | ||
|
||
```ts | ||
import { automockPlugin } from '@vitest/mocker/node' | ||
import { createServer } from 'vite' | ||
|
||
await createServer({ | ||
plugins: [ | ||
automockPlugin(), | ||
], | ||
}) | ||
``` | ||
|
||
Any module that has `mock=automock` or `mock=autospy` query will be mocked: | ||
|
||
```ts | ||
import { calculator } from './src/calculator.js?mock=automock' | ||
|
||
calculator(1, 2) | ||
calculator.mock.calls[0] === [1, 2] | ||
``` | ||
|
||
Ideally, you would inject those queries somehow, not write them manually. In the future, this package will support `with { mock: 'auto' }` syntax. | ||
|
||
> [!WARNING] | ||
> 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. | ||
> [!NOTE] | ||
> This plugin is included in `mockerPlugin`. | ||
## automockModule | ||
|
||
Replace every export with a mock in the code. | ||
|
||
```ts | ||
import { automockModule } from '@vitest/mocker/node' | ||
import { parseAst } from 'vite' | ||
|
||
const ms = await automockModule( | ||
`export function test() {}`, | ||
'automock', | ||
parseAst, | ||
) | ||
console.log( | ||
ms.toString(), | ||
ms.generateMap({ hires: 'boundary' }) | ||
) | ||
``` | ||
|
||
Produces this: | ||
|
||
```ts | ||
function test() {} | ||
|
||
const __vitest_es_current_module__ = { | ||
__esModule: true, | ||
test, | ||
} | ||
const __vitest_mocked_module__ = __vitest_mocker__.mockObject(__vitest_es_current_module__, 'automock') | ||
const __vitest_mocked_0__ = __vitest_mocked_module__.test | ||
export { | ||
__vitest_mocked_0__ as test, | ||
} | ||
``` | ||
|
||
## hoistMocksPlugin | ||
|
||
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. | ||
|
||
```ts | ||
import { hoistMocksPlugin } from '@vitest/mocker/node' | ||
import { createServer } from 'vite' | ||
|
||
await createServer({ | ||
plugins: [ | ||
hoistMocksPlugin({ | ||
hoistedModules: ['virtual:my-module'], | ||
regexpHoistable: /myObj.(mock|hoist)/, | ||
utilsObjectName: ['myObj'], | ||
hoistableMockMethodNames: ['mock'], | ||
// disable support for vi.mock(import('./path')) | ||
dynamicImportMockMethodNames: [], | ||
hoistedMethodNames: ['hoist'], | ||
}), | ||
], | ||
}) | ||
``` | ||
|
||
> [!NOTE] | ||
> This plugin is included in `mockerPlugin`. | ||
## hoistMocks | ||
|
||
Hoist compiler hints, replace static imports with dynamic ones and update exports access to make sure live-binding is not broken. | ||
|
||
This is required to ensure mocks are resolved before we import the user module. | ||
|
||
```ts | ||
import { parseAst } from 'vite' | ||
|
||
hoistMocks( | ||
` | ||
import { mocked } from './some-module.js' | ||
vi.mock('./some-module.js', () => { | ||
return { mocked: true } | ||
}) | ||
mocked === true | ||
`, | ||
'/my-module.js', | ||
parseAst | ||
) | ||
``` | ||
|
||
Produces this code: | ||
|
||
```js | ||
vi.mock('./some-module.js', () => { | ||
return { mocked: true } | ||
}) | ||
|
||
const __vi_import_0__ = await import('./some-module.js') | ||
__vi_import_0__.mocked === true | ||
``` | ||
|
||
## dynamicImportPlugin | ||
|
||
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. | ||
|
||
It doesn't make sense to use this plugin in isolation from other plugins. | ||
|
||
```ts | ||
import { dynamicImportPlugin } from '@vitest/mocker/node' | ||
import { createServer } from 'vite' | ||
|
||
await createServer({ | ||
plugins: [ | ||
dynamicImportPlugin({ | ||
globalThisAccessor: 'Symbol.for("my-mocker")' | ||
}), | ||
], | ||
}) | ||
``` | ||
|
||
```ts | ||
await import('./my-module.js') | ||
|
||
// produces this: | ||
await globalThis[`Symbol.for('my-mocker')`].wrapDynamicImport(() => import('./my-module.js')) | ||
``` | ||
|
||
## findMockRedirect | ||
|
||
This method will try to find a file inside `__mocks__` folder that corresponds to the current file. | ||
|
||
```ts | ||
import { findMockRedirect } from '@vitest/mocker/node' | ||
|
||
// uses sync fs APIs | ||
const mockRedirect = findMockRedirect( | ||
root, | ||
'vscode', | ||
'vscode', // if defined, will assume the file is a library name | ||
) | ||
// mockRedirect == ${root}/__mocks__/vscode.js | ||
``` |
Oops, something went wrong.