Assertions and matchers — what a good assertion actually checks
A test can pass while checking almost nothing meaningful — the gap between a real assertion and a weak one is one of the most common ways a test suite quietly stops protecting anything.
3 min read
A test that passes without actually verifying the thing it claims to
test("returns the user's full name", () => {
const result = getFullName({ first: "Ada", last: "Lovelace" });
expect(result).toBeTruthy(); // passes for "Ada", "wrong", "x", literally any non-empty string
});toBeTruthy() passes for any non-empty, non-zero, non-null value — it would pass just as happily if getFullName had a bug and returned "undefined undefined" or the wrong name entirely, as long as something non-empty came back. This test's name promises "returns the full name," but its assertion actually checks "returns something," which is a much weaker claim than the test's own name suggests — the exact gap that makes weak assertions dangerous: the test looks like coverage in a test report, but doesn't actually protect the behavior it claims to.
The fix: assert the specific expected value
test("returns the user's full name", () => {
const result = getFullName({ first: "Ada", last: "Lovelace" });
expect(result).toBe("Ada Lovelace"); // now a wrong implementation actually fails this test
});toBe() (or toEqual() for objects/arrays) checks the exact expected value, which means a real bug in getFullName — wrong order, missing space, wrong casing — now actually fails the test. The rule of thumb: an assertion should be specific enough that if you deliberately broke the function being tested, this exact test would be the one that caught it. If you can imagine a real bug that would still sail through the assertion unnoticed, the assertion isn't specific enough yet.
toBe vs toEqual: reference equality vs structural equality
const expected = { name: "Ada", role: "admin" };
expect(getUser()).toBe(expected); // fails even for an IDENTICAL-looking object —
// toBe checks reference equality (===)
expect(getUser()).toEqual(expected); // passes if the CONTENTS match, regardless of referenceThis is the same reference-vs-structural-equality distinction that shows up throughout JavaScript (and gets its own dedicated treatment in this platform's React domain, where it drives useEffect dependency comparisons): toBe is ===, which for objects and arrays means "the exact same object in memory," not "looks the same." Using toBe on an object or array almost always fails for the wrong reason — not because the data is wrong, but because it's a different (if identical-looking) object. toEqual compares structure — every key and value — which is what you actually want for objects and arrays in the overwhelming majority of tests.
Asserting on the right level of detail
// Too loose — barely tests anything
expect(response.status).toBeLessThan(500);
// Too brittle — breaks on any unrelated field ever being added to the response
expect(response.body).toEqual({
id: 123, name: "Ada", createdAt: "2026-08-31T10:00:00.000Z", updatedAt: "2026-08-31T10:00:00.000Z",
});
// Right level — checks exactly what this test is actually about
expect(response.status).toBe(200);
expect(response.body.name).toBe("Ada");Asserting on an entire object when the test is really only about one field means the test breaks every time an unrelated field is added or a timestamp changes — a real, common source of the "flaky test" problem the next-but-one lesson covers, and a reason contributors learn to distrust a suite that fails on unrelated changes. Assert precisely what the test's name claims it's checking, and nothing more.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Why is `expect(result).toBeTruthy()` often a weak assertion?
2. What's the actual difference between `toBe` and `toEqual`?
3. Why can asserting on an entire response object instead of just the relevant field make a test suite less trustworthy over time?