CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tap

A Test-Anything-Protocol library for JavaScript with comprehensive testing capabilities and plugin-based architecture

Pending
Overview
Eval results
Files

lifecycle-hooks.mddocs/

Lifecycle Hooks

Before and after hooks for test setup and teardown at multiple levels, providing comprehensive test lifecycle management.

Capabilities

Test-Level Hooks

Hooks that run before and after individual tests, useful for test-specific setup and cleanup.

/**
 * Run a function before the current test starts
 * @param fn - Function to run before test execution
 */
function before(fn: () => void | Promise<void>): void;

/**
 * Run a function after the current test completes
 * @param fn - Function to run after test execution
 */
function after(fn: () => void | Promise<void>): void;

Usage Examples:

import { test, before, after } from "tap";

test("test with setup and teardown", (t) => {
  let testData: any;
  
  before(async () => {
    // Setup runs before test assertions
    testData = await setupTestData();
    console.log("Test setup completed");
  });
  
  after(async () => {
    // Cleanup runs after test completes
    await cleanupTestData(testData);
    console.log("Test cleanup completed");
  });
  
  t.ok(testData, "test data should be available");
  t.end();
});

Subtest-Level Hooks

Hooks that run before and after each subtest, perfect for repetitive setup/teardown across multiple related tests.

/**
 * Run a function before each subtest
 * @param fn - Function to run before each subtest execution
 */
function beforeEach(fn: () => void | Promise<void>): void;

/**
 * Run a function after each subtest completes
 * @param fn - Function to run after each subtest execution
 */
function afterEach(fn: () => void | Promise<void>): void;

Usage Examples:

test("parent test with subtest hooks", (t) => {
  let sharedResource: any;
  
  beforeEach(async () => {
    // Runs before each subtest
    sharedResource = await createResource();
    console.log("Subtest setup completed");
  });
  
  afterEach(async () => {
    // Runs after each subtest
    await cleanupResource(sharedResource);
    console.log("Subtest cleanup completed");
  });
  
  t.test("first subtest", (st) => {
    st.ok(sharedResource, "resource available");
    st.end();
  });
  
  t.test("second subtest", (st) => {
    st.ok(sharedResource, "resource available");
    st.end();
  });
  
  t.end();
});

Hook Execution Order

Hooks execute in a predictable order relative to test execution:

  1. before hooks (outermost to innermost)
  2. Test execution begins
  3. beforeEach hooks (for subtests)
  4. Subtest execution
  5. afterEach hooks (for subtests)
  6. after hooks (innermost to outermost)
test("hook execution order", (t) => {
  before(() => console.log("1. Parent before"));
  
  beforeEach(() => console.log("2. Parent beforeEach"));
  
  after(() => console.log("6. Parent after"));
  
  afterEach(() => console.log("5. Parent afterEach"));
  
  t.test("subtest", (st) => {
    before(() => console.log("3. Subtest before"));
    
    after(() => console.log("4. Subtest after"));
    
    st.ok(true, "subtest assertion");
    st.end();
  });
  
  t.end();
});

Async Hook Patterns

Hooks support both synchronous and asynchronous execution:

test("async hooks", (t) => {
  before(async () => {
    // Async setup
    await new Promise(resolve => setTimeout(resolve, 100));
    console.log("Async setup completed");
  });
  
  after(() => {
    // Sync cleanup
    console.log("Sync cleanup completed");
  });
  
  beforeEach(async () => {
    // Each subtest gets fresh async setup
    await initializeSubtestData();
  });
  
  t.test("async subtest", async (st) => {
    const result = await performAsyncOperation();
    st.ok(result.success);
  });
  
  t.end();
});

Error Handling in Hooks

Errors in hooks will cause the test to fail:

test("hook error handling", (t) => {
  before(() => {
    throw new Error("Setup failed");
    // This will cause the test to fail before any assertions run
  });
  
  after(() => {
    // This still runs even if test or other hooks fail
    console.log("Cleanup always runs");
  });
  
  t.ok(true, "This assertion might not run if before hook fails");
  t.end();
});

Resource Management Patterns

Common patterns for managing resources with hooks:

// Database connection pattern
test("database tests", (t) => {
  let db: Database;
  
  before(async () => {
    db = await Database.connect(testConnectionString);
  });
  
  after(async () => {
    if (db) {
      await db.close();
    }
  });
  
  beforeEach(async () => {
    // Clean slate for each subtest
    await db.truncateAll();
    await db.seedTestData();
  });
  
  t.test("user operations", async (st) => {
    const user = await db.createUser({ name: "test" });
    st.ok(user.id);
  });
  
  t.test("product operations", async (st) => {
    const product = await db.createProduct({ name: "widget" });
    st.ok(product.id);
  });
  
  t.end();
});

// File system pattern
test("file operations", (t) => {
  let tempDir: string;
  
  before(async () => {
    tempDir = await fs.mkdtemp("/tmp/test-");
  });
  
  after(async () => {
    await fs.rmdir(tempDir, { recursive: true });
  });
  
  t.test("create file", async (st) => {
    const filePath = path.join(tempDir, "test.txt");
    await fs.writeFile(filePath, "test content");
    st.ok(await fs.pathExists(filePath));
  });
  
  t.end();
});

// HTTP server pattern
test("API tests", (t) => {
  let server: Server;
  let baseUrl: string;
  
  before(async () => {
    server = createTestServer();
    await server.listen(0); // Random port
    baseUrl = `http://localhost:${server.address().port}`;
  });
  
  after(async () => {
    await server.close();
  });
  
  beforeEach(() => {
    // Reset server state between tests
    server.resetMocks();
  });
  
  t.test("GET /users", async (st) => {
    const response = await fetch(`${baseUrl}/users`);
    st.equal(response.status, 200);
  });
  
  t.end();
});

Hook Scope and Inheritance

Hooks are scoped to their containing test and inherited by subtests:

test("parent", (t) => {
  before(() => console.log("Parent before"));
  beforeEach(() => console.log("Parent beforeEach"));
  
  t.test("child", (st) => {
    // Inherits parent hooks
    before(() => console.log("Child before"));
    
    st.test("grandchild", (gst) => {
      // Inherits both parent and child hooks
      gst.ok(true);
      gst.end();
    });
    
    st.end();
  });
  
  t.end();
});

Multiple Hooks

You can register multiple hooks of the same type:

test("multiple hooks", (t) => {
  before(() => console.log("First before"));
  before(() => console.log("Second before"));
  
  after(() => console.log("First after"));
  after(() => console.log("Second after"));
  
  // All hooks run in registration order
  t.ok(true);
  t.end();
});

Conditional Hooks

Hooks can be conditionally registered:

test("conditional setup", (t) => {
  if (process.env.ENABLE_SLOW_SETUP) {
    before(async () => {
      await performExpensiveSetup();
    });
  }
  
  if (needsCleanup) {
    after(() => {
      performCleanup();
    });
  }
  
  t.ok(true);
  t.end();
});

Install with Tessl CLI

npx tessl i tessl/npm-tap

docs

assertions.md

index.md

lifecycle-hooks.md

mocking.md

subprocess-testing.md

test-organization.md

tile.json