vitest/valid-expect
Require expect to get a value, end in a called matcher, and be awaited when it is async
expect(value) doesn't check anything by itself. The check happens when a matcher such as .toBe() is called, so an expect with no matcher, or with a matcher that isn't called, passes no matter what the value is.
.resolves and .rejects make the matcher return a promise. If the test doesn't await or return it, the test can end first, and a failed assertion doesn't fail the test.
Configure#
Off by default. To turn it on:
[lint.rules]
"vitest/valid-expect" = "error" # or "warn"Every option, set to its default:
[lint.rules]
"vitest/valid-expect" = ["error", {
alwaysAwait = false,
asyncMatchers = ["toResolve", "toReject"],
minArgs = 1,
maxArgs = 1,
}]Examples#
import { expect, test } from "vitest";
test("adds the tax", () => {
expect(total(100))Expect must have a corresponding matcher call.; // no matcher
expect(total(100)).toBeMatchers must be called to assert.; // the matcher is not called
expect()Expect requires at least 1 argument.toBe(108); // no value
expect(total(100)).never.toBe(0)Expect has an unknown modifier.; // `never` is not a modifier
});import { expect, test } from "vitest";
test("loads the user", () => {
expect(loadUser(1)).resolves.toEqual({ id: 1 })Async assertions must be awaited.;
});import { expect, test } from "vitest";
test("adds the tax", () => {
expect(total(100)).toBe(108);
expect(total(100)).not.toBe(0);
});
test("loads the user", async () => {
await expect(loadUser(1)).resolves.toEqual({ id: 1 });
});
test("rejects an unknown id", () => {
return expect(loadUser(-1)).rejects.toThrow("not found");
});A second argument that's a string or a template literal is the message to print on failure. It doesn't count toward maxArgs:
import { expect, test } from "vitest";
test("adds the tax", () => {
expect(total(100), "after tax").toBe(108);
});Options#
| Option | Default | What it does |
|---|---|---|
alwaysAwait | false | Also report an async assertion that's returned instead of awaited. |
asyncMatchers | ["toResolve", "toReject"] | Matchers that return a promise, so they must be awaited or returned. Your list replaces the default. |
minArgs | 1 | The fewest arguments expect can take. |
maxArgs | 1 | The most arguments expect can take. |
{ "alwaysAwait": true }1 problemimport { expect, test } from "vitest";
test("rejects an unknown id", () => {
return expect(loadUser(-1)).rejects.toThrow("not found")Async assertions must be awaited.;
});Use asyncMatchers for custom matchers that return a promise:
{ "asyncMatchers": ["toResolveWith"] }1 problemimport { expect, test } from "vitest";
test("loads the user", () => {
expect(loadUser(1)).toResolveWith({ id: 1 })Async assertions must be awaited.;
});A message that comes from a variable is reported. Raise maxArgs to allow it:
import { expect, test } from "vitest";
test("adds the tax", () => {
expect(total(100), message)Expect takes at most 1 argument.toBe(108);
});{ "maxArgs": 2 }No problemsimport { expect, test } from "vitest";
test("adds the tax", () => {
expect(total(100), message).toBe(108);
});