Skip to content
NewBun 1.4.5 is out. Release notes
AphrodyBun

Linter · Rules

bun:test/prefer-expect-assertions

Require expect.assertions() or expect.hasAssertions() at the start of every test

An expect in a catch, a callback or a loop might never run, and a test where no expect runs still passes. expect.hasAssertions() makes the test fail unless at least one assertion ran. expect.assertions(2) makes it fail unless exactly two ran.

The first statement of every test must be one of the two. The rule also reports expect.hasAssertions() with an argument, and expect.assertions() with anything but one number literal.

--fix-suggestions adds expect.hasAssertions().

Configure

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"bun:test/prefer-expect-assertions" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"bun:test/prefer-expect-assertions" = ["error", {
  onlyFunctionsWithAsyncKeyword = false,
  onlyFunctionsWithExpectInCallback = false,
  onlyFunctionsWithExpectInLoop = false,
}]

Examples

Reported:

import { expect, test } from "bun:test";

test("rejects a wrong password", async () => {
  try {
    await login("ada", "wrong");
  } catch (error) {
    expect(error).toBeInstanceOf(AuthError);
  }
});
import { expect, test } from "bun:test";

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

Not reported:

import { expect, test } from "bun:test";

test("rejects a wrong password", async () => {
  expect.assertions(1);
  try {
    await login("ada", "wrong");
  } catch (error) {
    expect(error).toBeInstanceOf(AuthError);
  }
});

A beforeEach or afterEach hook that calls expect.hasAssertions() covers the tests after it in the same describe, or in the rest of the file if it's at the top level:

import { beforeEach, expect, test } from "bun:test";

beforeEach(() => {
  expect.hasAssertions();
});

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

Options

Without options, every test is checked. If you turn on one or more, only tests that match at least one of them are checked.

OptionDefaultWhat it does
onlyFunctionsWithAsyncKeywordfalseCheck tests that are async functions.
onlyFunctionsWithExpectInCallbackfalseCheck tests that call expect inside a nested function.
onlyFunctionsWithExpectInLoopfalseCheck tests that call expect inside a loop.
import { expect, test } from "bun:test";

test("emits the saved user", () => {
  store.on("save", (user) => {
    expect(user.name).toBe("Ada");
  });
  store.save({ name: "Ada" });
});
import { expect, test } from "bun:test";

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

When not to use it

In a test that only calls expect at its top level, the extra line adds nothing. Use the options to check only the tests where an assertion can be skipped.