Best Practices
Habits that keep should() chains readable and their failures informative.
Pick the most specific verifier
The more specific the verifier, the more specific the failure. should().true(x !== null) fails with a generic message; should().string(x).defined() and every later check in that chain fails with context. Use true()/false() only for genuine boolean-context checks (see General Assertions).
One chain, one behavior
Chain checks that describe the same outcome, not a whole test in one line:
Assert positively rather than double-negated
not.contain(5) is fine; not.empty() usually isn't the point — length(n) or contain(expected) says what you actually mean. Reserve not for genuine absence assertions (Concepts explains the mechanics).
Give object collections an identifier
Every array check on objects compares by reference unless you pass the identifier function. Make it a reflex: objects in, (e) => e?.id in (see Arrays). Without it the failure says "doesn't contain the expected element" for data that looks identical — the least debuggable failure in the library.
Prefer compareOnly over ignoring for strict contracts
ignoring('updatedAt') passes no matter what other fields drift in. When the test owns the full expectation, compareOnly(...) enumerates exactly what matters — new unexpected fields then fail loudly instead of silently passing (see Objects).
Nullables: assert definedness explicitly
When absence is meaningful, say so — defined() or not.defined() as its own check. The implicit guard throws for every check anyway; an explicit assertion documents intent and fails at the right line (see Concepts).
Use date accuracy instead of arithmetic
equals(expected, 'day') replaces hand-computed day boundaries, and it documents the test's real tolerance (see Dates).
Assert behavior, not messages
Failure messages are readable by design, but they are a contract of the library, not of your code — don't build tests around matching their exact text.