packages/vitest/src/migrations/update-22-1-0/files/ai-instructions-for-vitest-4.md
These instructions guide you through migrating an Nx workspace containing multiple Vitest projects from Vitest 3.x to Vitest 4.0. Work systematically through each breaking change category.
Identify all Vitest projects:
nx show projects --with-target test
Locate all Vitest configuration files:
vitest.config.{ts,js,mjs}vitest.workspace.{ts,js,mjs}project.json files for inline Vitest configurationIdentify affected code:
**/*.{spec,test}.{ts,js,tsx,jsx}vi.fn(), vi.spyOn(), vi.mock()Search Pattern: coverage in all vitest.config.* files and project.json test target options
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
coverage: {
all: true,
extensions: ['.ts', '.tsx'],
ignoreEmptyLines: false,
experimentalAstAwareRemapping: true,
},
},
});
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
coverage: {
// Explicitly define files to include in coverage
include: ['src/**/*.{ts,tsx}'],
// Remove: all, extensions, ignoreEmptyLines, experimentalAstAwareRemapping
},
},
});
Action Items:
coverage.all optioncoverage.extensions optioncoverage.ignoreEmptyLines optioncoverage.experimentalAstAwareRemapping optioncoverage.include patterns based on project structureSearch Pattern: poolOptions, maxThreads, maxForks, singleThread, singleFork in all Vitest config files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
maxThreads: 4,
maxForks: 2,
singleThread: false,
poolOptions: {
threads: {
useAtomics: true,
},
vmThreads: {
memoryLimit: '512MB',
},
},
},
});
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
maxWorkers: 4, // Consolidates maxThreads and maxForks
isolate: true, // Replaces singleThread: false
// Remove: poolOptions, threads.useAtomics
vmMemoryLimit: '512MB', // Moved to top-level
},
});
Action Items:
maxThreads and maxForks with single maxWorkers optionsingleThread: true or singleFork: true with maxWorkers: 1, isolate: falsepoolOptions.* nested options to top-level (e.g., poolOptions.vmThreads.memoryLimit → vmMemoryLimit)threads.useAtomics optionVITEST_MAX_THREADS and VITEST_MAX_FORKS → VITEST_MAX_WORKERSSearch Pattern: workspace property in Vitest config files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
workspace: ['apps/*', 'libs/*'],
},
});
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
projects: ['apps/*', 'libs/*'],
},
});
Action Items:
workspace property to projects in all config filespoolMatchGlobs to use projects pattern matching insteadenvironmentMatchGlobs to use projects pattern matching insteadSearch Pattern: browser.provider, browser.testerScripts, imports from @vitest/browser
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
browser: {
enabled: true,
provider: 'playwright', // String value
testerScripts: ['./setup.js'],
},
},
});
// Import changes
import { page } from '@vitest/browser';
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
browser: {
enabled: true,
provider: { name: 'playwright' }, // Object value
testerHtmlPath: './test-setup.html', // Renamed from testerScripts
},
},
});
// Import changes
import { page } from 'vitest/browser';
Action Items:
browser.provider string values to object format: { name: 'provider-name' }browser.testerScripts with browser.testerHtmlPath@vitest/browser to vitest/browser@vitest/browser from dependencies if no longer neededSearch Pattern: deps.external, deps.inline, deps.fallbackCJS in config files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
deps: {
external: ['some-package'],
inline: ['inline-package'],
fallbackCJS: true,
},
},
});
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
server: {
deps: {
external: ['some-package'],
inline: ['inline-package'],
fallbackCJS: true,
},
},
},
});
Action Items:
deps.* options under server.deps namespacepoolMatchGlobs (use projects with conditions instead)environmentMatchGlobs (use projects with conditions instead)Search Pattern: .getMockName() calls in test files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
const mockFn = vi.fn();
expect(mockFn.getMockName()).toBe('spy'); // Old default
// ✅ AFTER (Vitest 4.0)
const mockFn = vi.fn();
expect(mockFn.getMockName()).toBe('vi.fn()'); // New default
// If you need custom names, set them explicitly
const namedMock = vi.fn().mockName('myCustomName');
expect(namedMock.getMockName()).toBe('myCustomName');
Action Items:
'spy' to 'vi.fn()'.mockName() calls where specific names are requiredSearch Pattern: .mock.invocationCallOrder in test files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
const mockFn = vi.fn();
mockFn();
expect(mockFn.mock.invocationCallOrder[0]).toBe(0); // Started at 0
// ✅ AFTER (Vitest 4.0)
const mockFn = vi.fn();
mockFn();
expect(mockFn.mock.invocationCallOrder[0]).toBe(1); // Now starts at 1 (Jest-compatible)
Action Items:
invocationCallOrder to account for 1-based indexingSearch Pattern: vi.spyOn on constructors, vi.fn() used as constructors
Changes Required:
// ❌ BEFORE (Vitest 3.x) - Arrow function constructors might have worked
const MockConstructor = vi.fn(() => ({ value: 42 }));
new MockConstructor(); // May have worked in v3
// ✅ AFTER (Vitest 4.0) - Must use function or class
const MockConstructor = vi.fn(function () {
return { value: 42 };
});
new MockConstructor(); // Correctly supports 'new'
// Or use class syntax
class MockClass {
value = 42;
}
const MockConstructor = vi.fn(MockClass);
Action Items:
function keyword or class syntaxnew keyword works correctlySearch Pattern: vi.restoreAllMocks() in test files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
vi.mock('./module', () => ({ fn: vi.fn() }));
vi.restoreAllMocks(); // Would restore automocks
// ✅ AFTER (Vitest 4.0)
vi.mock('./module', () => ({ fn: vi.fn() }));
vi.restoreAllMocks(); // Only restores manual spies, NOT automocks
// To reset automocks, use:
vi.unmock('./module');
// or
vi.resetModules();
Action Items:
vi.restoreAllMocks() usagevi.unmock() or vi.resetModules() calls for automocked modulesSearch Pattern: vi.spyOn() on already mocked functions
Changes Required:
// ❌ BEFORE (Vitest 3.x)
const mock = vi.fn();
const spy = vi.spyOn({ method: mock }, 'method');
// spy !== mock (created new spy)
// ✅ AFTER (Vitest 4.0)
const mock = vi.fn();
const spy = vi.spyOn({ method: mock }, 'method');
// spy === mock (returns same instance)
Action Items:
Search Pattern: vi.mock() with factory functions, .mockRestore() on automocks
Changes Required:
// ❌ BEFORE (Vitest 3.x)
vi.mock('./utils', () => ({
get value() {
return 42;
}, // Would call getter
}));
import { value } from './utils';
console.log(value); // Would execute getter logic
// Restore might have worked
const spy = vi.spyOn(obj, 'method');
spy.mockRestore(); // Might work on automocks
// ✅ AFTER (Vitest 4.0)
vi.mock('./utils', () => ({
get value() {
return 42;
},
}));
import { value } from './utils';
console.log(value); // Returns undefined (doesn't call getter)
// Explicitly return value if needed
vi.mock('./utils', () => ({
value: 42, // Not a getter
}));
// mockRestore no longer works on automocks
const spy = vi.spyOn(obj, 'method');
spy.mockRestore(); // Throws error if method is automocked
// Use unmock instead
vi.unmock('./module');
Action Items:
.mockRestore() calls on automocked methodsvi.unmock() to clear automocks insteadSearch Pattern: .mock.settledResults in test files
Changes Required:
// ✅ AFTER (Vitest 4.0)
const asyncMock = vi.fn(async () => 'result');
const promise = asyncMock();
// settledResults is immediately populated with 'incomplete' status
expect(asyncMock.mock.settledResults[0]).toEqual({
type: 'incomplete',
value: undefined,
});
// After promise resolves
await promise;
expect(asyncMock.mock.settledResults[0]).toEqual({
type: 'fulfilled',
value: 'result',
});
Action Items:
settledResults before promise resolution'incomplete' status in assertionsSearch Pattern: Custom reporters, onCollected, onTaskUpdate, onFinished
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default {
onCollected(files) {
// Handle collected files
},
onTaskUpdate(task) {
// Handle task update
},
onFinished(files) {
// Handle completion
},
};
// ✅ AFTER (Vitest 4.0)
// Use new reporter API - consult Vitest 4 docs for replacement methods
Action Items:
Search Pattern: reporters: ['basic'], reporters: ['verbose']
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
reporters: ['basic'],
},
});
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
reporters: [['default', { summary: false }]], // Equivalent to 'basic'
},
});
// For verbose (tree output)
reporters: ['tree']; // Use 'tree' for hierarchical output
Action Items:
'basic' reporter with ['default', { summary: false }]'verbose' reporter with 'tree' for hierarchical outputSearch Pattern: Snapshot tests involving custom elements or Web Components
Changes Required:
// ✅ AFTER (Vitest 4.0)
// Shadow root contents now printed by default in snapshots
// If you want old behavior (don't print shadow root):
export default defineConfig({
test: {
printShadowRoot: false,
},
});
Action Items:
printShadowRoot: false if old behavior is requiredSearch Pattern: CI/CD configuration files, .env files, documentation
Changes Required:
# ❌ BEFORE (Vitest 3.x)
VITEST_MAX_THREADS=4
VITEST_MAX_FORKS=2
VITE_NODE_DEPS_MODULE_DIRECTORIES=/custom/path
# ✅ AFTER (Vitest 4.0)
VITEST_MAX_WORKERS=4
VITEST_MODULE_DIRECTORIES=/custom/path
Action Items:
.env filesVITEST_MAX_THREADS, VITEST_MAX_FORKS, VITE_NODE_DEPS_MODULE_DIRECTORIESSearch Pattern: vitest/execute, __vitest_executor, vite-node
Changes Required:
// ❌ BEFORE (Vitest 3.x)
import { execute } from 'vitest/execute';
// Access to __vitest_executor
// ✅ AFTER (Vitest 4.0)
// Use Vite's Module Runner API instead
// Consult Vite Module Runner documentation
Action Items:
vitest/execute, migrate to Vite Module Runner__vitest_executorSearch Pattern: TypeScript imports from vitest, type errors after upgrade
Changes Required:
// All deprecated type exports removed
// If you get TypeScript errors about missing types:
// - Check if you're using deprecated type names
// - Update to current type names from Vitest 4 API
// - Remove explicit @types/node if it was only needed due to Vitest bug
Action Items:
@types/node usage (may no longer be accidentally included)# Test each project individually
nx run-many -t test -p PROJECT_NAME
# Run tests across all affected projects
nx affected -t test
# Verify coverage generation works with new config
nx affected -t test --coverage
# Run full CI validation
nx prepush
Solution: Add explicit coverage.include patterns to match your source files
Solution: Convert arrow functions used as constructors to function keyword or class syntax
Solution: Use vi.unmock() or vi.resetModules() instead of vi.restoreAllMocks()
Solution: Update to 1-based indexing for invocationCallOrder
Solution: Check browser provider is object format and imports use vitest/browser
Solution: Update to new type definitions and remove usage of deprecated types
Create a checklist of all files that need review:
# Configuration files
find . -name "vitest.config.*" -o -name "vitest.workspace.*"
find . -name "project.json" -exec grep -l "vitest" {} \;
# Test files
find . -name "*.spec.*" -o -name "*.test.*"
# Files with mock usage
rg "vi\.(fn|spyOn|mock|restoreAllMocks)" --type ts --type tsx --type js
# Files with coverage config
rg "coverage\.(all|extensions|ignoreEmptyLines)" --type ts --type js
# CI configuration
find . -name ".github/workflows/*.yml" -o -name ".gitlab-ci.yml" -o -name "azure-pipelines.yml"
# Find all vitest configurations
nx show projects --with-target test
# Test specific project after changes
nx test PROJECT_NAME
# Test all affected
nx affected -t test
# View project details
nx show project PROJECT_NAME --web
# Clear Nx cache if needed
nx reset
DO NOT
expect(true).toBe(true)When executing this migration: