Skip to main content
Bun’s test runner provides lifecycle hooks to set up and tear down test state. These hooks are compatible with Jest.

Available hooks

beforeAll

Runs once before all tests in a describe block:
import { describe, test, beforeAll, expect } from "bun:test";

describe("database tests", () => {
  let db;

  beforeAll(async () => {
    // Runs once before all tests
    db = await connectToDatabase();
  });

  test("query 1", () => {
    expect(db.query("SELECT 1")).toBe(1);
  });

  test("query 2", () => {
    expect(db.query("SELECT 2")).toBe(2);
  });
});

beforeEach

Runs before each test:
import { describe, test, beforeEach, expect } from "bun:test";

describe("counter tests", () => {
  let counter;

  beforeEach(() => {
    // Runs before each test
    counter = 0;
  });

  test("increment", () => {
    counter++;
    expect(counter).toBe(1);
  });

  test("increment twice", () => {
    counter++;
    counter++;
    expect(counter).toBe(2);
  });
});

afterEach

Runs after each test:
import { describe, test, afterEach } from "bun:test";

describe("cleanup tests", () => {
  afterEach(() => {
    // Clean up after each test
    cleanupTempFiles();
  });

  test("creates temp file", () => {
    createTempFile("test1.txt");
  });

  test("creates another temp file", () => {
    createTempFile("test2.txt");
  });
});

afterAll

Runs once after all tests in a describe block:
import { describe, test, beforeAll, afterAll } from "bun:test";

describe("server tests", () => {
  let server;

  beforeAll(async () => {
    server = await startServer();
  });

  afterAll(async () => {
    // Runs once after all tests
    await server.stop();
  });

  test("responds to requests", async () => {
    const response = await fetch(server.url);
    expect(response.status).toBe(200);
  });
});

Async hooks

All hooks support async operations:
import { beforeEach, afterEach, test } from "bun:test";

beforeEach(async () => {
  await database.connect();
  await database.seed();
});

afterEach(async () => {
  await database.clear();
  await database.disconnect();
});

test("database query", async () => {
  const result = await database.query("SELECT * FROM users");
  expect(result).toHaveLength(5);
});

Hook execution order

Hooks execute in the following order:
  1. beforeAll (outer describe)
  2. beforeAll (inner describe)
  3. beforeEach (outer describe)
  4. beforeEach (inner describe)
  5. test
  6. afterEach (inner describe)
  7. afterEach (outer describe)
  8. afterAll (inner describe)
  9. afterAll (outer describe)
Example:
import { describe, test, beforeAll, beforeEach, afterEach, afterAll } from "bun:test";

describe("outer", () => {
  beforeAll(() => console.log("outer beforeAll"));
  beforeEach(() => console.log("outer beforeEach"));
  afterEach(() => console.log("outer afterEach"));
  afterAll(() => console.log("outer afterAll"));

  test("outer test", () => {
    console.log("outer test");
  });

  describe("inner", () => {
    beforeAll(() => console.log("inner beforeAll"));
    beforeEach(() => console.log("inner beforeEach"));
    afterEach(() => console.log("inner afterEach"));
    afterAll(() => console.log("inner afterAll"));

    test("inner test", () => {
      console.log("inner test");
    });
  });
});

// Output:
// outer beforeAll
// outer beforeEach
// outer test
// outer afterEach
// inner beforeAll
// outer beforeEach
// inner beforeEach
// inner test
// inner afterEach
// outer afterEach
// inner afterAll
// outer afterAll

Scoping

Hooks are scoped to the describe block they’re defined in:
import { describe, test, beforeEach } from "bun:test";

beforeEach(() => {
  // Runs before all tests in file
  console.log("global beforeEach");
});

describe("suite 1", () => {
  beforeEach(() => {
    // Only runs before tests in suite 1
    console.log("suite 1 beforeEach");
  });

  test("test 1", () => {});
});

describe("suite 2", () => {
  beforeEach(() => {
    // Only runs before tests in suite 2
    console.log("suite 2 beforeEach");
  });

  test("test 2", () => {});
});

Timeout

Hooks can have custom timeouts:
import { beforeAll } from "bun:test";

beforeAll(async () => {
  // Custom timeout of 30 seconds
  await slowSetup();
}, 30000);

Done callback

Hooks support the done callback pattern:
import { beforeEach } from "bun:test";

beforeEach((done) => {
  setTimeout(() => {
    // Setup complete
    done();
  }, 100);
});
Prefer using async/await over done callbacks when possible.

Error handling

If a hook throws an error:
  • beforeAll/beforeEach: The test is marked as failed
  • afterEach/afterAll: The test result is preserved, but the error is logged
import { beforeEach, test, expect } from "bun:test";

beforeEach(() => {
  if (!setupSuccessful()) {
    throw new Error("Setup failed");
  }
});

test("will fail if setup fails", () => {
  expect(true).toBe(true);
});

Resource cleanup with using

Bun supports JavaScript’s using keyword for automatic resource cleanup:
import { test } from "bun:test";

test("automatic cleanup", () => {
  using server = createTestServer();
  // server.stop() called automatically when test ends
  
  const response = fetch(server.url);
  expect(response.status).toBe(200);
});

Build docs developers (and LLMs) love