A Test-Anything-Protocol library for JavaScript with comprehensive testing capabilities and plugin-based architecture
—
Before and after hooks for test setup and teardown at multiple levels, providing comprehensive test lifecycle management.
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();
});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();
});Hooks execute in a predictable order relative to test execution:
before hooks (outermost to innermost)beforeEach hooks (for subtests)afterEach hooks (for subtests)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();
});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();
});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();
});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();
});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();
});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();
});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