> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/oven-sh/bun/llms.txt
> Use this file to discover all available pages before exploring further.

# Writing Tests

> Learn how to write tests using Bun's Jest-compatible API, including async tests, matchers, test modifiers, and parameterized tests.

Define tests with a Jest-like API imported from the built-in `bun:test` module. All test functions (`test`, `describe`, `expect`, `beforeAll`, etc.) are also available as globals — no import required — for full Jest compatibility.

## Basic usage

```typescript math.test.ts theme={null}
import { expect, test } from "bun:test";

test("adds numbers", () => {
  expect(1 + 1).toBe(2);
});
```

### Grouping tests

Use `describe()` to group related tests into a suite:

```typescript math.test.ts theme={null}
import { expect, test, describe } from "bun:test";

describe("arithmetic", () => {
  test("addition", () => {
    expect(2 + 2).toBe(4);
  });

  test("multiplication", () => {
    expect(2 * 3).toBe(6);
  });
});
```

### Async tests

Tests can be async. Bun awaits the returned promise before marking the test as complete.

```typescript theme={null}
import { expect, test } from "bun:test";

test("resolves a value", async () => {
  const result = await Promise.resolve(42);
  expect(result).toBe(42);
});
```

Alternatively, use the `done` callback. If you declare it as a parameter, you **must** call it or the test will time out.

```typescript theme={null}
test("done callback", done => {
  Promise.resolve(42).then(result => {
    expect(result).toBe(42);
    done();
  });
});
```

### Timeouts

Pass a timeout in milliseconds as the third argument to `test()`. The default is `5000ms`.

```typescript theme={null}
test("slow operation", async () => {
  const data = await slowOperation();
  expect(data).toBeDefined();
}, 500); // must complete in under 500ms
```

## Test modifiers

### test.skip

Skip a test without deleting it. Skipped tests are not executed.

```typescript theme={null}
test.skip("floating point precision", () => {
  expect(0.1 + 0.2).toEqual(0.3); // TODO: fix
});
```

### test.todo

Mark a test as a planned but unimplemented case:

```typescript theme={null}
test.todo("implement retry logic");
```

Run `bun test --todo` to find any todo tests that are unexpectedly passing.

### test.only

Run only the tests (or suites) marked with `.only`. Use `bun test --only` to enforce this.

```typescript theme={null}
test("test #1", () => {
  // skipped with --only
});

test.only("test #2", () => {
  // runs
});

describe.only("suite", () => {
  test("test #3", () => {
    // runs
  });
});
```

### test.if / test.skipIf / test.todoIf

Run, skip, or mark a test as todo based on a runtime condition:

```typescript theme={null}
const isMacOS = process.platform === "darwin";

test.if(isMacOS)("runs only on macOS", () => { /* ... */ });

test.skipIf(isMacOS)("skipped on macOS", () => { /* ... */ });

test.todoIf(isMacOS)("not yet implemented on macOS", () => { /* ... */ });
```

These modifiers also work on `describe` blocks, affecting all tests within.

### test.failing

Use `test.failing` to mark a test you know is currently broken. The test passes when it fails, and fails when it unexpectedly passes:

```typescript theme={null}
test.failing("known floating point issue", () => {
  expect(0.1 + 0.2).toBe(0.3); // fails due to float precision — expected
});
```

### test.concurrent / test.serial

Control concurrency at the individual test level. See [Test Runner](/test/overview) for details.

## Assertion counting

Use `expect.assertions()` to verify a specific number of assertions ran — essential for async code with conditional branches:

```typescript theme={null}
test("exactly two assertions", () => {
  expect.assertions(2);

  expect(1 + 1).toBe(2);
  expect("hello").toContain("ell");
});
```

Use `expect.hasAssertions()` to require at least one assertion:

```typescript theme={null}
test("async assertion runs", async () => {
  expect.hasAssertions();

  const data = await fetchData();
  expect(data).toBeDefined();
});
```

## Retries and repeats

```typescript theme={null}
// Retry up to 3 times on failure
test("flaky test", async () => {
  const res = await fetch("https://api.example.com");
  expect(res.ok).toBe(true);
}, { retry: 3 });

// Run 21 times total (1 initial + 20 repeats) — fails if any iteration fails
test("stability check", () => {
  expect(Math.random()).toBeLessThan(1);
}, { repeats: 20 });
```

<Note>You cannot use both `retry` and `repeats` on the same test.</Note>

## Parameterized tests

### test.each

Run the same test with multiple sets of data:

```typescript theme={null}
test.each([
  [1, 2, 3],
  [3, 4, 7],
])("%i + %i should equal %i", (a, b, expected) => {
  expect(a + b).toBe(expected);
});
```

Pass objects to access values by name in the title using `$key` syntax:

```typescript theme={null}
test.each([
  { a: 1, b: 2, expected: 3 },
  { a: 4, b: 5, expected: 9 },
])("add($a, $b) = $expected", ({ a, b, expected }) => {
  expect(a + b).toBe(expected);
});
```

### describe.each

Create a parameterized suite that runs all its tests for each case:

```typescript theme={null}
describe.each([
  [1, 2, 3],
  [3, 4, 7],
])("add(%i, %i)", (a, b, expected) => {
  test(`returns ${expected}`, () => {
    expect(a + b).toBe(expected);
  });

  test("result is greater than each input", () => {
    expect(a + b).toBeGreaterThan(a);
    expect(a + b).toBeGreaterThan(b);
  });
});
```

### Format specifiers

| Specifier | Description            |
| --------- | ---------------------- |
| `%p`      | pretty-format          |
| `%s`      | String                 |
| `%d`      | Number                 |
| `%i`      | Integer                |
| `%f`      | Floating point         |
| `%j`      | JSON                   |
| `%o`      | Object                 |
| `%#`      | Index of the test case |
| `%%`      | Literal `%`            |

## Type testing

Bun includes `expectTypeOf` for type-level assertions, compatible with Vitest. These are no-ops at runtime — run `bunx tsc --noEmit` to validate types.

```typescript theme={null}
import { expectTypeOf } from "bun:test";

expectTypeOf(123).toBeNumber();
expectTypeOf("hello").toBeString();
expectTypeOf([1, 2, 3]).items.toBeNumber();
expectTypeOf(Promise.resolve(42)).resolves.toBeNumber();
```

## Matchers reference

### Basic

| Matcher            | Description                    |
| ------------------ | ------------------------------ |
| `.toBe()`          | Strict equality (`===`)        |
| `.toEqual()`       | Deep equality                  |
| `.toStrictEqual()` | Deep equality with type checks |
| `.toBeNull()`      | Value is `null`                |
| `.toBeUndefined()` | Value is `undefined`           |
| `.toBeDefined()`   | Value is not `undefined`       |
| `.toBeTruthy()`    | Value is truthy                |
| `.toBeFalsy()`     | Value is falsy                 |
| `.toBeNaN()`       | Value is `NaN`                 |
| `.not`             | Negates the matcher            |

### Strings and arrays

| Matcher               | Description                           |
| --------------------- | ------------------------------------- |
| `.toContain()`        | String or array contains a value      |
| `.toHaveLength()`     | Array or string has a specific length |
| `.toMatch()`          | String matches regex or substring     |
| `.toContainEqual()`   | Array contains an equal element       |
| `.stringContaining()` | Expect helper for string substring    |
| `.arrayContaining()`  | Expect helper for array subset        |

### Objects

| Matcher               | Description                           |
| --------------------- | ------------------------------------- |
| `.toMatchObject()`    | Object matches a subset of properties |
| `.toHaveProperty()`   | Object has a specific property path   |
| `.objectContaining()` | Expect helper for object subset       |

### Numbers

| Matcher                     | Description                       |
| --------------------------- | --------------------------------- |
| `.toBeGreaterThan()`        | Greater than a value              |
| `.toBeGreaterThanOrEqual()` | Greater than or equal to a value  |
| `.toBeLessThan()`           | Less than a value                 |
| `.toBeLessThanOrEqual()`    | Less than or equal to a value     |
| `.toBeCloseTo()`            | Approximately equal (floating pt) |

### Functions and errors

| Matcher             | Description                     |
| ------------------- | ------------------------------- |
| `.toThrow()`        | Function throws an error        |
| `.toBeInstanceOf()` | Value is an instance of a class |
| `.resolves`         | Promise resolves to a value     |
| `.rejects`          | Promise rejects with an error   |

### Mock functions

| Matcher                      | Description                             |
| ---------------------------- | --------------------------------------- |
| `.toHaveBeenCalled()`        | Mock was called at least once           |
| `.toHaveBeenCalledTimes(n)`  | Mock was called exactly `n` times       |
| `.toHaveBeenCalledWith(...)` | Mock was called with specific arguments |
| `.toHaveReturned()`          | Mock returned without throwing          |
| `.toHaveReturnedWith(value)` | Mock returned a specific value          |

### Snapshots

| Matcher                                 | Description                       |
| --------------------------------------- | --------------------------------- |
| `.toMatchSnapshot()`                    | Matches a stored snapshot         |
| `.toMatchInlineSnapshot()`              | Matches an inline snapshot        |
| `.toThrowErrorMatchingSnapshot()`       | Error message matches snapshot    |
| `.toThrowErrorMatchingInlineSnapshot()` | Error message matches inline snap |

## Best practices

<AccordionGroup>
  <Accordion title="Use descriptive test names">
    ```typescript theme={null}
    // Good
    test("returns 404 when user does not exist", () => { /* ... */ });

    // Avoid
    test("user test", () => { /* ... */ });
    ```
  </Accordion>

  <Accordion title="Group related tests with describe">
    ```typescript theme={null}
    describe("User authentication", () => {
      describe("with valid credentials", () => {
        test("returns user data", () => { /* ... */ });
        test("sets auth token", () => { /* ... */ });
      });

      describe("with invalid credentials", () => {
        test("throws AuthError", () => { /* ... */ });
      });
    });
    ```
  </Accordion>

  <Accordion title="Use specific matchers">
    ```typescript theme={null}
    // Good
    expect(users).toHaveLength(3);
    expect(user.email).toContain("@");

    // Avoid
    expect(users.length === 3).toBe(true);
    ```
  </Accordion>

  <Accordion title="Test error conditions">
    ```typescript theme={null}
    test("throws on invalid input", () => {
      expect(() => validateEmail("not-an-email")).toThrow("Invalid email");
    });

    test("rejects with network error", async () => {
      await expect(fetchUser("bad-id")).rejects.toThrow("User not found");
    });
    ```
  </Accordion>
</AccordionGroup>
