Projects
Run different test configurations in the same Vitest process.
v5 Config Inheritance & Nested Projects
- Inline projects inherit the root config by default —
extendsnow defaults totrue, so root Vite options (plugins,resolve.alias) and test options are inherited. Arrays likesetupFilesare appended, not replaced. Opt out withextends: false, or inherit from another file withextends: './vitest.shared.ts'. Projects referenced as config files/directories still don't inherit the root. - Referenced config files can declare their own
projects— such a config acts as a container providing nested projects namedapp (unit),app (e2e), etc. In v4 a referenced config'sprojectsfield was silently ignored, so audit merged configs that pull one in. - Inline projects share the declaring config's Vite server by default (
sharedViteServer) — the declaring config runs once, so pluginconfighooks no longer run per project. A project gets its own server only when it changes the Vite config (plugins,alias,css,deps.optimizer,root,browser,mode). SetsharedViteServer: falseif a plugin must be re-instantiated per project.
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()], // inherited by every inline project (v4 needed extends: true)
test: {
projects: [
{ test: { name: 'unit', include: ['**/*.unit.test.ts'] } },
// a package that declares its own projects becomes a nested container:
'./packages/app/vitest.config.ts', // -> "app (unit)", "app (e2e)", ...
],
},
})Basic Projects Setup
// vitest.config.ts
defineConfig({
test: {
projects: [
// Glob patterns for config files
'packages/*',
// Inline config
{
test: {
name: 'unit',
include: ['tests/unit/**/*.test.ts'],
environment: 'node',
},
},
{
test: {
name: 'integration',
include: ['tests/integration/**/*.test.ts'],
environment: 'jsdom',
},
},
],
},
})Monorepo Pattern
defineConfig({
test: {
projects: [
// Each package has its own vitest.config.ts
'packages/core',
'packages/cli',
'packages/utils',
],
},
})Package config:
// packages/core/vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
name: 'core',
include: ['src/**/*.test.ts'],
environment: 'node',
},
})Different Environments
Run same tests in different environments:
defineConfig({
test: {
projects: [
{
test: {
name: 'happy-dom',
root: './shared-tests',
environment: 'happy-dom',
setupFiles: ['./setup.happy-dom.ts'],
},
},
{
test: {
name: 'node',
root: './shared-tests',
environment: 'node',
setupFiles: ['./setup.node.ts'],
},
},
],
},
})Browser + Node Projects
defineConfig({
test: {
projects: [
{
test: {
name: 'unit',
include: ['tests/unit/**/*.test.ts'],
environment: 'node',
},
},
{
test: {
name: 'browser',
include: ['tests/browser/**/*.test.ts'],
browser: {
enabled: true,
name: 'chromium',
provider: 'playwright',
},
},
},
],
},
})Shared Configuration
// vitest.shared.ts
export const sharedConfig = {
testTimeout: 10000,
setupFiles: ['./tests/setup.ts'],
}
// vitest.config.ts
import { sharedConfig } from './vitest.shared'
defineConfig({
test: {
projects: [
{
test: {
...sharedConfig,
name: 'unit',
include: ['tests/unit/**/*.test.ts'],
},
},
{
test: {
...sharedConfig,
name: 'e2e',
include: ['tests/e2e/**/*.test.ts'],
},
},
],
},
})Project-Specific Dependencies
Each project can have different dependencies inlined:
defineConfig({
test: {
projects: [
{
test: {
name: 'project-a',
server: {
deps: {
inline: ['package-a'],
},
},
},
},
],
},
})Running Specific Projects
# Run specific project (v5 adds the -p shorthand)
vitest --project unit
vitest -p integration
# Multiple projects / wildcards
vitest --project unit --project e2e
vitest --project="packages*"
# Exclude a project
vitest --project="!browser"
# Nested projects: --project matches the prefix
vitest -p app # every project of the "app" config
vitest -p "app (unit)" # just one nested projectProviding Values to Projects
Share values from config to tests:
// vitest.config.ts
defineConfig({
test: {
projects: [
{
test: {
name: 'staging',
provide: {
apiUrl: 'https://staging.api.com',
debug: true,
},
},
},
{
test: {
name: 'production',
provide: {
apiUrl: 'https://api.com',
debug: false,
},
},
},
],
},
})
// In tests, use inject
import { inject } from 'vitest'
test('uses correct api', () => {
const url = inject('apiUrl')
expect(url).toContain('api.com')
})With Fixtures
const test = base.extend({
apiUrl: ['/default', { injected: true }],
})
test('uses injected url', ({ apiUrl }) => {
// apiUrl comes from project's provide config
})Per-Project Pool & Isolation (v4)
Since the v4 pool rework, isolation, parallelism, and Node CLI options can be set per project:
defineConfig({
test: {
projects: [
{
test: {
name: 'unit',
isolate: false, // fast, non-isolated unit tests
exclude: ['**/*.integration.test.ts'],
},
},
{
test: {
name: 'sequential',
include: ['**/*.sequential.test.ts'],
fileParallelism: false, // run these files one at a time
},
},
{
test: {
name: 'staging',
execArgv: ['--env-file=.env.staging'], // per-project Node flags
},
},
],
},
})Global Setup per Project
defineConfig({
test: {
projects: [
{
test: {
name: 'with-db',
globalSetup: ['./tests/db-setup.ts'],
},
},
],
},
})Key Points
- Projects run in same Vitest process (replaces the removed
workspaceoption) - Each project can have different environment, pool, isolation, and config
- Use glob patterns for monorepo packages
- Run specific projects with
--project(supports wildcards and!exclusion) - Use
provideto inject config values into tests - Inline projects inherit root config by default (v5
extends: true); setextends: falseto opt out - Referenced configs that declare
projectsprovide nested projects (name (child))