Files
stack/packages/mosaic/framework/skills/vitest/references/features-context.md
T
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00

4.3 KiB

name, description
name description
test-context-fixtures Test context, custom fixtures with test.extend

Test Context & Fixtures

Built-in Context

Every test receives context as first argument:

test('context', ({ task, expect, skip }) => {
  console.log(task.name); // Test name
  expect(1).toBe(1); // Context-bound expect
  skip(); // Skip test dynamically
});

Context Properties

  • task - Test metadata (name, file, etc.)
  • expect - Expect bound to this test (important for concurrent tests)
  • skip(condition?, message?) - Skip the test
  • onTestFinished(fn) - Cleanup after test
  • onTestFailed(fn) - Run on failure only

Custom Fixtures with test.extend

Create reusable test utilities:

import { test as base } from 'vitest';

// Define fixture types
interface Fixtures {
  db: Database;
  user: User;
}

// Create extended test
export const test = base.extend<Fixtures>({
  // Fixture with setup/teardown
  db: async ({}, use) => {
    const db = await createDatabase();
    await use(db); // Provide to test
    await db.close(); // Cleanup
  },

  // Fixture depending on another fixture
  user: async ({ db }, use) => {
    const user = await db.createUser({ name: 'Test' });
    await use(user);
    await db.deleteUser(user.id);
  },
});

Using fixtures:

test('query user', async ({ db, user }) => {
  const found = await db.findUser(user.id);
  expect(found).toEqual(user);
});

Fixture Initialization

Fixtures only initialize when accessed:

const test = base.extend({
  expensive: async ({}, use) => {
    console.log('initializing'); // Only runs if test uses it
    await use('value');
  },
});

test('no fixture', () => {}); // expensive not called
test('uses fixture', ({ expensive }) => {}); // expensive called

Auto Fixtures

Run fixture for every test:

const test = base.extend({
  setup: [
    async ({}, use) => {
      await globalSetup();
      await use();
      await globalTeardown();
    },
    { auto: true }, // Always run
  ],
});

Scoped Fixtures

File Scope

Initialize once per file:

const test = base.extend({
  connection: [
    async ({}, use) => {
      const conn = await connect();
      await use(conn);
      await conn.close();
    },
    { scope: 'file' },
  ],
});

Worker Scope

Initialize once per worker:

const test = base.extend({
  sharedResource: [
    async ({}, use) => {
      await use(globalResource);
    },
    { scope: 'worker' },
  ],
});

Injected Fixtures (from Config)

Override fixtures per project:

// test file
const test = base.extend({
  apiUrl: ['/default', { injected: true }],
});

// vitest.config.ts
defineConfig({
  test: {
    projects: [
      {
        test: {
          name: 'prod',
          provide: { apiUrl: 'https://api.prod.com' },
        },
      },
    ],
  },
});

Scoped Values per Suite

Override fixture for specific suite:

const test = base.extend({
  environment: 'development',
});

describe('production tests', () => {
  test.scoped({ environment: 'production' });

  test('uses production', ({ environment }) => {
    expect(environment).toBe('production');
  });
});

test('uses default', ({ environment }) => {
  expect(environment).toBe('development');
});

Extended Test Hooks

Type-aware hooks with fixtures:

const test = base.extend<{ db: Database }>({
  db: async ({}, use) => {
    const db = await createDb();
    await use(db);
    await db.close();
  },
});

// Hooks know about fixtures
test.beforeEach(({ db }) => {
  db.seed();
});

test.afterEach(({ db }) => {
  db.clear();
});

Composing Fixtures

Extend from another extended test:

// base-test.ts
export const test = base.extend<{ db: Database }>({
  db: async ({}, use) => {
    /* ... */
  },
});

// admin-test.ts
import { test as dbTest } from './base-test';

export const test = dbTest.extend<{ admin: User }>({
  admin: async ({ db }, use) => {
    const admin = await db.createAdmin();
    await use(admin);
  },
});

Key Points

  • Use { } destructuring to access fixtures
  • Fixtures are lazy - only initialize when accessed
  • Return cleanup function from fixtures
  • Use { auto: true } for setup fixtures
  • Use { scope: 'file' } for expensive shared resources
  • Fixtures compose - extend from extended tests