vitest/prefer-called-exactly-once-with

Require toHaveBeenCalledExactlyOnceWith() over separate call count and argument checks

fixable

toHaveBeenCalledOnce() followed by toHaveBeenCalledWith(...) on the same mock takes two assertions to say one thing: the mock ran once, with these arguments. toHaveBeenCalledExactlyOnceWith(...) says it in one line.

The pair is reported when both assertions are in the same block, in either order. Assertions with .not are ignored.

--fix-dangerously replaces the pair with one toHaveBeenCalledExactlyOnceWith(...) call. --fix doesn't.

Configure#

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"vitest/prefer-called-exactly-once-with" = "error" # or "warn"

Examples#

example.ts1 problem
import { expect, test, vi } from "vitest";

test("saves the draft", () => {
  const save = vi.fn();
  autosave("draft", save);
  expect(save).toHaveBeenCalledOnce();
  expect(save).toHaveBeenCalledWith("draft");
});
example.tsNo problems
import { expect, test, vi } from "vitest";

test("saves the draft", () => {
  const save = vi.fn();
  autosave("draft", save);
  expect(save).toHaveBeenCalledExactlyOnceWith("draft");
});

A mockClear(), mockReset(), or mockRestore() on the mock between the two assertions means they're about different calls, so they aren't a pair:

example.tsNo problems
import { expect, test, vi } from "vitest";

test("saves again after an edit", () => {
  const save = vi.fn();
  autosave("draft", save);
  expect(save).toHaveBeenCalledOnce();

  save.mockClear();
  autosave("edited", save);
  expect(save).toHaveBeenCalledWith("edited");
});