Skip to content
Ben is currently available for contract work for 3D & web solutions — reach out.

Back to Blog Listing

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.