Identity, equality, and the bug between them
toBe is Object.is: the same thing, not an equal thing. Use it for numbers, strings, booleans and "this is literally that object". Use toEqual for anything structural.
expect({ qty: 2 }).toBe({ qty: 2 }); // FAILS — two different objects
expect({ qty: 2 }).toEqual({ qty: 2 }); // passes — the same values
expect(2).toBe(2); // passes — primitives are compared by value
expect([1, 2]).toEqual([1, 2]); // passes
Object.is(NaN, NaN) is true, which is what you want in a test — NaN === NaN is false, and a matcher built on === makes "expected NaN to be NaN" fail, which has confused people for years.
What deep equality has to decide
- Key order does not matter.
{ a: 1, b: 2 }equals{ b: 2, a: 1 }. - Missing versus undefined does matter in most runners:
{ a: 1 }does not equal{ a: 1, b: undefined }. Jest hastoStrictEqualfor the strict reading andtoEqualfor the forgiving one — know which yours is. - Types matter: an array is not an object with numeric keys, and a
Datecompares by its time, not by its fields. - Maps and Sets compare by contents, which naive implementations forget.
Errors
Matching the message matters more than it looks: a test that only asserts "something threw" passes when the code throws a completely different error — a TypeError from a typo, for instance — and you have a green test over a broken function.
expect(() => parse("")).toThrow(); // it threw
expect(() => parse("")).toThrow(/empty/); // …and the message matches
expect(parse("")).toThrow(); // WRONG: it throws before expect is reached
await expect(load("/missing")).rejects.toThrow(); // the async version, in Jest/Vitest
A message worth reading
You read a failure message far more often than you write the assertion. Prefer the matcher that knows what it is comparing — toHaveLength(3) over expect(x.length).toBe(3), toContain over indexOf(...) !== -1 — because when it fails it prints the array, not a boolean.
assert(total(line) === 1780);
// AssertionError: false == true
expect(total(line)).toBe(1780);
// expected 1782 to be 1780
expect(total(line)).toBe(1780); // with a structural diff, in a real runner
// - Expected 1780
// + Received 1782
Custom matchers, and when they earn their place
When the same three assertions appear in ten tests, put them in a helper. The test then says what it means — "this response is ok" — and the helper is the single place to change when "ok" changes.
expect(response).toBeOk(); // status 200–299, and the body parsed
expect(order).toBeValidOrder(); // every invariant, in one place
// in this course's small kit, that is simply a function
const expectOk = (res) => {
expect(res.status).toBe(200);
expect(res.body).toBeTruthy();
};