bun:test/prefer-expect-assertions

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

has suggestions

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#

example.ts1 problem
import { expect, test } from "bun:test";

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

test("adds", () => {
  expect.assertions("1");
  expect(1 + 1).toBe(2);
});
example.tsNo problems
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:

example.tsNo problems
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.
example.tswith { "onlyFunctionsWithExpectInCallback": true }1 problem
import { expect, test } from "bun:test";

test("emits the saved user", () => {
  store.on("save", (user) => {
    expect(user.name).toBe("Ada");
  });
  store.save({ name: "Ada" });
});
example.tswith { "onlyFunctionsWithExpectInCallback": true }No problems
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.