Fix Vitest: Cannot find namespace vi in TypeScript
Keep mock return values checked against the real function signature, and verify both TypeScript compilation and module mocking at runtime.
TL;DR#
vi.Mock fails because the imported vi is a runtime helper, not a namespace exporting mock types. After vi.mock() has replaced a module, use vi.mocked(importedFunction) to access its mock methods with the original signature preserved. For an explicit type assertion, import Mock directly from vitest and use Mock<typeof importedFunction>. Neither a type assertion nor vi.mocked() creates the runtime mock.
The original Stack Overflow question migrated a Vue Router test from Jest. Replacing jest.Mock with vi.Mock left this failing expression:
import { vi } from 'vitest';
import { useRoute } from './route';
vi.mock('./route');
const routeMock = useRoute as vi.Mock;The local reproduction used a small route module instead of installing Vue Router. TypeScript printed:
page.test.ts(10,33): error TS2503: Cannot find namespace 'vi'.Reproduced on 2 November 2026 with Vitest 5.0.5, TypeScript 7.0.2 and Node 24.22.2 in the official node:24-slim Docker image. Changing only the expression useRoute as vi.Mock to vi.mocked(useRoute) made the compiler pass. An actual Vitest run then passed the module-mocking test and two controls covering standalone mock typing and the helper's runtime behaviour.
Why it happens#
Vitest has separate runtime utilities and exported types. Its utility implementation exports vi as a value with the VitestUtils interface. Calls such as vi.mock(), vi.fn() and vi.spyOn() are methods on that value. Writing vi.Mock in a type position instead asks TypeScript to find an exported type named Mock inside a namespace named vi.
That namespace is not the API Vitest provides. The package declarations export mock types directly from the package. The accepted answer to the source question identifies this distinction; the useful addition for current tests is to retain the original function's signature instead of asserting a bare, broadly typed mock.
There is another boundary to preserve: runtime replacement does not rewrite the imported function's TypeScript declaration. After vi.mock('./route'), TypeScript still sees useRoute through its original exported signature. The vi.mocked documentation describes the helper that adds the corresponding mock-aware type. It returns the supplied value rather than replacing its implementation.
The practical consequence is that a successful type check answers only part of the question. The test runner must also supply a real mock function. Otherwise a cast can silence TypeScript while leaving a function that has no mock methods when the test executes.
Fix#
1. Keep the test runner and compiler checks explicit#
For a new, empty reproduction directory, install the tested versions:
npm init -y
npm install --save-dev vitest@5.0.5 typescript@7.0.2 @types/node@26.6.6For an existing project, retain its package manager and lockfile. This error does not by itself require an upgrade: the important change is how the mock is referenced. The pins above identify the environment used to check these examples.
Create this tsconfig.json for the isolated example:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"types": [
"node"
]
},
"include": [
"*.ts"
]
}This configuration supports the example's imports and Promise types. It is not the remedy for the missing namespace. All test utilities below are imported explicitly, so no Vitest globals configuration is needed. Enabling a global vi would not turn it into a namespace containing Mock.
If changing your setup produces a separate TS2688 error listing ordinary packages, inspect the typeRoots mistake that includes all of node_modules. That failure concerns type-library discovery and needs its own configuration correction.
2. Give the module exports concrete contracts#
Create route.ts. The function deliberately returns a small route-shaped object so the test remains independent of a framework:
export function useRoute(): { name: string } {
return { name: 'live' };
}Create users.ts with a genuinely asynchronous contract:
export interface User {
id: string;
name: string;
}
export async function loadUser(id: string): Promise<User> {
return { id, name: 'Real user' };
}Create page.ts, the consumer that the test will exercise:
import { useRoute } from './route';
import { loadUser } from './users';
export async function pageLabel(id: string): Promise<string> {
const user = await loadUser(id);
return useRoute().name + ':' + user.name;
}Both dependencies have visible return types. That lets the compiler validate the test's stub data instead of accepting an arbitrary object. In a Vue Router project, keep the library's real return type: a minimal object with only name may not satisfy the full route contract. The small route above is only this runnable fixture's contract.
3. Replace the namespace assertion with vi.mocked#
Create page.test.ts:
import { expect, test, vi } from 'vitest';
import { useRoute } from './route';
import { loadUser } from './users';
import { pageLabel } from './page';
vi.mock('./route');
vi.mock('./users');
test('uses the mocked route and async user', async () => {
const routeMock = vi.mocked(useRoute);
routeMock.mockReturnValue({ name: 'account' });
const userMock = vi.mocked(loadUser);
userMock.mockResolvedValue({ id: 'u1', name: 'Ada' });
expect(vi.isMockFunction(useRoute)).toBe(true);
expect(await pageLabel('u1')).toBe('account:Ada');
expect(userMock).toHaveBeenCalledWith('u1');
});The two vi.mock() calls perform the runtime module replacement. The two vi.mocked() calls expose mock methods to TypeScript. The consumer assertion proves that pageLabel sees the replaced dependencies, rather than merely checking a disconnected standalone stub.
Vitest documents module mocking and its import transformation. In normal test files, it hoists vi.mock() so the mock registration happens before the imported modules are evaluated. Import vi directly from Vitest as shown; wrapping it in your own utility module can prevent Vitest from recognising the hoisted call.
Verify the fix#
Run both checks from the reproduction directory:
npx tsc --noEmit
npx vitest run --maxWorkers=1The first command should exit successfully without the namespace diagnostic. The second should pass page.test.ts. In the recorded reproduction, the same test ran alongside the two controls below, and both checks passed again after replacing the helper with an explicit imported mock type.
Check the quality of the typing as well as the absence of an error. Temporarily remove name from the route return object or from the resolved user object. TypeScript should reject those incomplete values. The reproduction also compiled a contracts file with expected-error annotations for these invalid values; the compiler confirmed that the invalid synchronous and asynchronous replacements were rejected.
If the import now reports Module has no exported member, check the exported name and the installed package declarations. That diagnostic concerns the module's exports. Adding a namespace declaration for vi would obscure the original mistake and does not repair an incorrect import.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Explicit Mock types and asynchronous return values#
An explicit assertion remains useful when a variable needs a named mock type. This is a complete alternative for page.test.ts:
import { expect, test, vi } from 'vitest';
import type { Mock } from 'vitest';
import { useRoute } from './route';
vi.mock('./route');
test('uses an explicitly typed module mock', () => {
const routeMock = useRoute as Mock<typeof useRoute>;
routeMock.mockReturnValue({ name: 'account' });
expect(useRoute()).toEqual({ name: 'account' });
});Use a type-only import for Mock. The generic parameter in the tested Vitest API is the function signature, so Mock<typeof useRoute> preserves its arguments and result. Do not copy an older mock example that supplies separate return and argument generics without checking your installed Vitest declarations.
For a newly created mock, skip the assertion entirely. Save this as signature.test.ts alongside users.ts:
import { expect, test, vi } from 'vitest';
import type { Mock } from 'vitest';
import type { loadUser } from './users';
test('keeps the async function signature', async () => {
const userMock: Mock<typeof loadUser> = vi.fn<typeof loadUser>();
userMock.mockResolvedValue({ id: 'u2', name: 'Lin' });
expect(await userMock('u2')).toEqual({ id: 'u2', name: 'Lin' });
expect(userMock).toHaveBeenCalledWith('u2');
});Here vi.fn() actually creates the runtime mock. The signature generic constrains it; it does not attach the mock to users.ts or replace imports elsewhere. Use the module-mocking example when testing pageLabel's imported dependency.
The mock API reference explains the return-value difference. For an async function, mockResolvedValue() accepts the resolved User object. mockReturnValue() instead expects the function's actual return type, which is Promise of User in this example. To use that method, supply Promise.resolve({ id: 'u2', name: 'Lin' }). Passing the bare object to a correctly typed async mock's mockReturnValue() should fail compilation.
When the namespace error turns into a runtime TypeError#
Removing vi.mock() while keeping vi.mocked() creates a misleading result: the compiler accepts mock methods, but the function remains real. Save this independent control as helper-only.test.ts:
import { expect, test, vi } from 'vitest';
test('mocked changes typing without creating a runtime mock', () => {
const real = (name: string): string => 'Hello ' + name;
const typed = vi.mocked(real);
expect(typed).toBe(real);
expect(vi.isMockFunction(typed)).toBe(false);
expect(typed('Ada')).toBe('Hello Ada');
expect(() => typed.mockReturnValue('stub')).toThrow(TypeError);
});This control passed in the reproduction. It establishes why a type assertion alone is insufficient: it cannot add a mock implementation or a mockReturnValue property to an ordinary function. Choose the appropriate runtime operation first, then apply the mock-aware typing to the resulting function.
For suites being migrated from Jest, module-format failures can appear separately from this namespace error. If the next failure concerns import.meta in a Vite application, continue with the Jest and Vite import.meta reproduction. Once imports execute under the intended runner, keep both compiler and runtime checks in your verification: a typed mock is useful only when the consumer actually receives it.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix Cannot find name 'console' in TypeScript (TS2584)
console.log fails to type-check in a Node project. See which two tsconfig settings hide console, why the compiler hint points the wrong way, and the fix.
Fix Cannot find name 'AbortSignal' in axios typings
Which axios releases trigger TS2304, why upgrading axios no longer clears it, and the tsconfig fixes reproduced on TypeScript 4.5.2 and 7.0.2.
Fix Cannot find type definition file for 'body-parser'
Why one broken folder under node_modules/@types fails the whole project, why restarting VS Code sometimes clears it, and what TypeScript 6.0 changed.


