Assertions

Choose the matcher that fails for the right reason, with a message you can act on.

03of 16
≈ 12Min read
5Parts

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.

the first thing everybody trips over
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, not ===

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 has toStrictEqual for the strict reading and toEqual for the forgiving one — know which yours is.
  • Types matter: an array is not an object with numeric keys, and a Date compares 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.

pass the function, do not call it
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.

the same failure, three ways
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.

one concept, asserted in ten tests
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();
};

Practice