vitest-dev/vitest · error · Error
${hoistedNodes.size} call${plural ? 's' : ''} in "${relative
Error message
${hoistedNodes.size} call${plural ? 's' : ''} in "${relative(options.root || process.cwd(), id)}" ${plural ? 'were' : 'was'} defined outside of the module's top level scope:
${Array.from(hoistedNodes, (invalidNode) => `- ${getNodeName(getNodeCall(invalidNode))}${location}`).join('\n')}
Although ${plural ? 'they appear nested, they' : 'it appears nested, it'} will be hoisted and executed before anything in this file. Move ${plural ? 'them' : 'it'} to the top level to reflect ${plural ? 'their' : 'its'} actual execution order.
See: https://vitest.dev/guide/mocking/modules#how-it-works What it means
Thrown at packages/mocker/src/node/hoistMocks.ts:507-527 when, after hoisting analysis, hoistedNodes still contains calls that are not at the module's top level (and import.meta.vitest was not used). vi.mock/unmock/hoisted are hoisted to the top of the file by the transformer regardless of where they are written; nesting them in a function/block is misleading because they actually run first. Vitest errors to force the code to reflect real execution order.
Source
Thrown at packages/mocker/src/node/hoistMocks.ts:526
const locations = createIndexLocationsMap(code)
const map = options.getMap && new TraceMap(options.getMap() as any)
const plural = hoistedNodes.size > 1
const message = [
`${hoistedNodes.size} call${plural ? 's' : ''} in "${relative(options.root || process.cwd(), id)}" ${plural ? 'were' : 'was'} defined outside of the module's top level scope:`,
'',
...Array.from(hoistedNodes, (invalidNode) => {
const currentLocation = locations.get(invalidNode.start)
const originalLocation = map && currentLocation && originalPositionFor(map, currentLocation)
const location = originalLocation?.column != null && originalLocation?.line != null
? ` at ${relative(options.root || process.cwd(), id)}:${originalLocation.line}:${originalLocation.column + 1}`
: ''
return `- ${getNodeName(getNodeCall(invalidNode))}${location}`
}),
'',
`Although ${plural ? 'they appear nested, they' : 'it appears nested, it'} will be hoisted and executed before anything in this file. Move ${plural ? 'them' : 'it'} to the top level to reflect ${plural ? 'their' : 'its'} actual execution order.`,
'See: https://vitest.dev/guide/mocking/modules#how-it-works',
].join('\n')
throw new Error(message)
}
}
// hoist vi.mock/vi.hoisted
for (const node of arrayNodes) {
const end = getNodeTail(code, node)
// don't hoist into itself if it's already at the top
if (hoistIndex === end || hoistIndex === node.start) {
hoistIndex = end
}
else {
s.move(node.start, end, hoistIndex)
}
}
// hoist actual dynamic imports last so they are inserted after all hoisted mocks
for (const { node: importNode, id: importId } of imports) {
const source = importNode.source.value as stringView on GitHub (pinned to d568f8ce37)
Solutions
- Move the vi.mock/vi.unmock/vi.hoisted call to the top level of the test module.
- For conditionals or per-test mocks, use vi.doMock/vi.doUnmock (which are NOT hoisted and can live inside functions/tests).
- If reusing across files, keep mocks at the top level of each file or use a shared setup file.
Example fix
// before
function setup() {
vi.mock('./logger')
}
it('logs', () => { setup(); /* ... */ })
// after (top-level, hoisted)
vi.mock('./logger')
it('logs', () => { /* ... */ })
// or use doMock for runtime-controlled mocking
it('logs', () => {
vi.doMock('./logger')
/* ... */
}) Defensive patterns
Strategy: validation
Validate before calling
// Static check: ensure vi.mock/unmock/hoisted calls are at top level.
// At review time, grep for these calls inside function/if/for blocks.
//
// Example guard helper that uses the non-hoisted API (doMock) when conditional:
function mockConditionally(path: string, enabled: boolean) {
if (!enabled) return
// vi.doMock is NOT hoisted, so it is safe inside a function
vi.doMock(path)
} Prevention
- Keep vi.mock/vi.unmock/vi.hoisted at the top level of the test module.
- Use vi.doMock/vi.doUnmock for per-test or conditional mocking (these are not hoisted).
- Do not wrap vi.mock calls in helper functions; place them inline at the top of the file.
- Configure ESLint rules or grep checks to flag vi.mock inside nested scopes.
When it happens
Trigger: Placing vi.mock(...), vi.unmock(...), or vi.hoisted(...) inside a function body, an if/else block, a loop, a callback, or any non-top-level scope. The check at hoistMocks.ts:499-507 walks ast.body and removes top-level nodes; anything left over triggers the error.
Common situations: Conditionally mocking based on a flag: `if (cond) vi.mock('./x')`; wrapping mocks in a helper function for reuse; mocking inside beforeAll; attempting per-test mocks with vi.mock instead of vi.doMock.
Related errors
- vi.mock() expects a string path, but received a ${typeof pat
- vi.doMock() expects a string path, but received a ${typeof p
- There are some problems in resolving the mocks API. You may
- [vitest] vi.mock("${raw}", factory?: () => unknown) is not r
- vi.mock() expects a string path, but received a ${typeof pat
AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03).
Data as JSON: /data/errors/6e05a49d4fc62897.json.
Report an issue: GitHub.