"The test name is the spec. The assertion is the proof."
The Vocabulary You'll Use 90% of the Time
Vitest ships with the Chai-style expect() API plus Jest-compatible extensions. There are dozens of matchers; you'll reach for about a dozen of them daily, and the rest live in the docs for when you need them. The handful below are the ones to internalize first.
toBe(value)— strict equality (===). For primitives and reference equality of objects.toEqual(value)— deep structural equality. For objects and arrays where you care about values, not identity.toStrictEqual(value)— liketoEqual, but also rejects extraundefinedproperties and prototype mismatches. Use when shape matters.toContain(item)— arrays and strings only. Cheaper to read than indexing or includes() checks.toMatch(regex)— string matches a regex. Better than two separate assertions for length and presence.toThrow()/toThrow(message)— wrap the throwing call in an arrow function. Without the arrow, the throw happens beforeexpectsees it.toBeCloseTo(value, precision)— for floats. Use this any time you're comparing computed decimals;0.1 + 0.2 !== 0.3is real.resolves/rejects— Promise modifiers.await expect(promise).resolves.toEqual(...)reads cleanly without try/catch.
describe / it / expect — The Skeleton
describe groups related tests. it (or test) registers one. expect asserts. The skeleton is uniform across Vitest, Jest, and most modern runners; the differences are in matcher availability and config, not the shape.
describe can nest. A common pattern is the outer describe naming the unit under test (a function, a component), inner describes grouping by behavior or input class, and it blocks naming concrete behaviors.
Message Hygiene
Every test will fail eventually — usually in CI, with someone trying to understand what broke from terminal output alone. Three habits buy back hours of debugging:
- Name tests as sentences. Not
test('userService'). Notit('works'). Tryit('returns null when the user is not found'). - Use the second arg to
expect()for context the matcher can't carry:expect(result, 'after second fetch').toEqual(...). This shows up in the failure message. - Assert one thing per test when possible. Multiple assertions are fine when they describe the same behavior, but when they're testing different facets, split. A test that fails halfway through hides the other failures.
Negative Assertions Need Care
expect(x).not.toBe(y) reads fine but proves a weaker statement than positive matches. "x is not 5" doesn't tell you what x actually is. Prefer positive assertions where you can — expect(x).toBeLessThan(5) over expect(x).not.toBeGreaterThan(4).