Vitest Global Setup
A test database or API server needs to be ready before the first test and cleaned up after the last. Vitest's globalSetup handles both through one async function that can return its own cleanup.
Ben Houston • • 4 min read
Vitest's globalSetup feature runs code once before all tests execute, and optionally a cleanup function after all tests complete. Tested on Vitest 2.x through 4.x.
How Global Setup Works#
Vitest loads your setup file in a separate module context, runs its default export once before any test file starts, and, if that function returned a cleanup function, calls it after the whole suite finishes. Common uses:
- Setting up database connections
- Configuring environment variables
- Starting test servers
- Initializing shared resources
Setting Up the Global Setup File#
Create a file that exports a default async function. Vitest calls it before any tests run. The function receives a TestProject parameter that provides access to Vitest's project configuration.
Basic Structure#
// src/test-utils/beforeTests.ts import type { TestProject } from 'vitest/node'; export default async function setup(project: TestProject): Promise<void> { console.log('Starting global setup for project:', project.name); // Your setup code here console.log('Global setup complete!'); }
With Cleanup Function#
The setup function can return a cleanup function that will be executed after all tests complete:
// src/test-utils/beforeTests.ts import type { TestProject } from 'vitest/node'; import afterTests from './afterTests.js'; // the method name doesn't matter, just that it is the default export export default async function beforeTests(project: TestProject): Promise<() => Promise<void>> { console.log('starting beforeTests for project:', project.name); // Perform setup tasks await new Promise((resolve) => setTimeout(resolve, 1000)); console.log('finished beforeTests'); // Return cleanup function that runs after all tests return afterTests; }
The Teardown File#
Create a separate file for your cleanup logic (or you could combine it with the setup file):
// src/test-utils/afterTests.ts export default async function afterTests(): Promise<void> { console.log('starting afterTests'); // Perform cleanup tasks await new Promise((resolve) => setTimeout(resolve, 1000)); console.log('finished afterTests'); }
Configuring Vitest#
Add the globalSetup option to your vitest.config.ts:
// vitest.config.ts import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { globals: true, environment: 'node', globalSetup: ['./src/test-utils/beforeTests.ts'], }, });
The globalSetup option accepts an array of file paths. Vitest runs these files in order before any tests start. If a setup file returns a cleanup function, Vitest calls it after all tests complete, in reverse order.
Passing Configuration to Tests#
Use process.env to pass values from global setup to your tests:
// src/test-utils/beforeTests.ts import type { TestProject } from 'vitest/node'; export default async function setup(project: TestProject): Promise<() => Promise<void>> { console.log('Starting global setup for project:', project.name); // Set environment variables that tests can access const DATABASE_URL = 'postgresql://localhost:5432/test'; process.env['DATABASE_URL'] = DATABASE_URL; console.log('DATABASE_URL', DATABASE_URL); // Set other configuration values process.env['API_KEY'] = 'test-api-key'; process.env['NODE_ENV'] = 'test'; // Your setup code here await new Promise((resolve) => setTimeout(resolve, 1000)); console.log('Global setup complete!'); return async () => { // Cleanup: optionally clear environment variables console.log('Cleanup complete!'); }; }
Using Configuration in Tests#
Your tests can now access these environment variables:
// src/database.test.ts import { describe, it, expect } from 'vitest'; describe('Database', () => { it('should connect using DATABASE_URL', () => { const dbUrl = process.env['DATABASE_URL']; expect(dbUrl).toBe('postgresql://localhost:5432/test'); // Use dbUrl in your test... }); });
Common Use Cases#
Database Setup#
export default async function setup(): Promise<() => Promise<void>> { // Connect to test database const db = await connectToDatabase(); process.env['DATABASE_URL'] = db.connectionString; // Run migrations await db.migrate(); return async () => { // Clean up test data await db.cleanup(); await db.close(); }; }
API Server Setup#
export default async function setup(): Promise<() => Promise<void>> { // Start test server const server = await startTestServer(); process.env['API_URL'] = `http://localhost:${server.port}`; return async () => { // Stop server await server.stop(); }; }
Environment Configuration#
export default async function setup(): Promise<() => Promise<void>> { // Set all test environment variables process.env['NODE_ENV'] = 'test'; process.env['LOG_LEVEL'] = 'error'; process.env['API_TIMEOUT'] = '5000'; return async () => { // Optionally restore original values delete process.env['NODE_ENV']; }; }
Conclusion#
The whole pattern comes down to one thing: export a default async function that optionally returns a cleanup function. Vitest runs the setup before any tests start and calls the cleanup after they finish, so your suite gets shared resources without any per-test wiring.