Skip to content

Commit

Permalink
test: check support for custom manual mocks
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Aug 19, 2024
1 parent 3d8c345 commit fac977c
Show file tree
Hide file tree
Showing 16 changed files with 938 additions and 350 deletions.
2 changes: 1 addition & 1 deletion docs/api/vi.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ console.log(cart.getApples()) // still 42!
:::

::: tip
It is not possible to spy on an 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.
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.

```ts
import { calculator } from './src/calculator.ts'
Expand Down
289 changes: 289 additions & 0 deletions packages/mocker/EXPORTS.md
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
```
Loading

0 comments on commit fac977c

Please sign in to comment.