Repterm
Guides

Fixtures & Hooks

How to use hooks and lazy fixtures for test setup and teardown

Overview

Repterm provides lifecycle hooks (beforeAll, afterAll, beforeEach, afterEach) for managing test setup and teardown. beforeEach/afterEach are always named fixtures -- a lazy initialization pattern where setup code only runs if a test actually requests the fixture by name.

Lifecycle Hooks

beforeAll / afterAll

Run once before or after all tests in a describe block. beforeAll can return shared state that is accessible in subsequent hooks and tests.

import { test, describe, expect, beforeAll, afterAll } from 'repterm';

describe('suite', () => {
  beforeAll(async () => {
    // Create a shared directory for the entire suite
    const rootDir = '/tmp/repterm-suite';
    await $`mkdir -p ${rootDir}`;
    return { rootDir };
  });

  afterAll(async ({ rootDir }) => {
    await $`rm -rf ${rootDir}`;
  });

  test('uses shared dir', async ({ $, rootDir }) => {
    const result = await $`ls ${rootDir}`;
    await expect(result).toSucceed();
  });
});

beforeEach / afterEach

Run before or after every test, but only if the test requests the fixture by name. Both always require a name parameter.

describe('per-test setup', () => {
  beforeEach('workspace', async () => {
    await $`mkdir -p /tmp/test-workspace`;
    return '/tmp/test-workspace';
  });

  afterEach('workspace', async (workspace) => {
    await $`rm -rf ${workspace}`;
  });

  // 'workspace' fixture runs because it's requested
  test('has workspace', async ({ $, workspace }) => {
    const result = await $`ls ${workspace}`;
    await expect(result).toSucceed();
  });

  // 'workspace' fixture does NOT run for this test
  test('no workspace needed', async ({ $ }) => {
    await $`echo hello`;
  });
});

Named Fixtures (Lazy Hooks)

Named fixtures are a powerful pattern for on-demand setup. A named beforeEach only executes if the test function includes that fixture name as a parameter. This avoids unnecessary setup for tests that do not need it.

import { test, describe, expect, beforeAll, beforeEach, afterEach, afterAll } from 'repterm';

describe('fixtures', () => {
  beforeAll(async () => ({ rootDir: '/tmp/repterm-suite' }));

  // Named fixture: only runs if a test requests 'tmpDir'
  beforeEach('tmpDir', async ({ rootDir }) => {
    const tmpDir = `${rootDir}/${Date.now()}`;
    await $`mkdir -p ${tmpDir}`;
    return tmpDir;
  });

  // Cleanup for the named fixture
  afterEach('tmpDir', async (tmpDir) => {
    await $`rm -rf ${tmpDir}`;
  });

  afterAll(async ({ rootDir }) => {
    await $`rm -rf ${rootDir}`;
  });

  // The 'tmpDir' fixture runs because the test requests it
  test('uses fixture', async ({ $, tmpDir }) => {
    await $`touch ${tmpDir}/a.txt`;
    const result = await $`ls ${tmpDir}`;
    await expect(result).toContainInOutput('a.txt');
  });

  // The 'tmpDir' fixture does NOT run for this test
  test('skips fixture', async ({ $ }) => {
    const result = await $`echo "no fixture needed"`;
    await expect(result).toSucceed();
  });
});

Key Concepts

Lazy Execution

Named hooks are lazy. They only execute when a test function destructures the fixture by name. If a test does not request the fixture, the setup and teardown for that fixture are skipped entirely. This keeps tests fast and avoids unnecessary side effects.

Shared State from beforeAll

The return value of beforeAll becomes shared context. It is merged into the context object available to beforeEach hooks and test functions. Use this for expensive, suite-level setup that should happen only once.

Fixture Cleanup

When a named afterEach is defined, it only runs for tests where the corresponding named beforeEach actually executed. The fixture's return value is passed as an argument to the cleanup function.

When to Use Each Hook

HookScopeUse for
beforeAllOnce per suiteDatabase connections, shared directories, heavy initialization
afterAllOnce per suiteClosing connections, removing shared resources
Named beforeEachOnly when requestedOptional fixtures that not every test needs
Named afterEachOnly when requestedCleanup for the corresponding named fixture