Prompt
When writing tests, validate the actual user-visible behavior and structured error data, not just a substring or a indirectly implied result. Make failures unambiguous (no swallow-pass patterns) and keep tests easy to debug.
Apply this standard:
- Prefer full message assertions (especially for formatted/translated strings) and assert structured fields like
error.path/error.detailswhen those are part of the contract. - Avoid patterns that can accidentally always pass (e.g., empty
try/catchwithout assertions inside). - For behavior with control-flow semantics (e.g.,
fatal), add tests that prove the flow stops/continues, not only that an error exists. - Cover realistic inputs and edge cases; add additional cases when current regex/validation logic is suspected incomplete.
- Improve debugging and maintenance with table-driven tests (
test.each/ equivalent) instead of ad-hoc concatenation. - Where type-level expectations matter, add type tests (e.g., with
tsd) rather than relying solely on compile-time errors.
Example (meaningful runtime assertions):
const result = schema.safeParse(badInput);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues[0].message).toBe("full expected message");
expect(result.error.issues[0].path).toEqual(['points']);
}
Example (prove fatal stops):
const schema = z
.object({ test: z.literal(true) })
.nullable()
.refine(v => v !== null, { message: 'foo', fatal: true })
.superRefine((v, ctx) => { if (v && (v as any).test) ctx.addIssue({ code: 'custom', message: 'bar' }); });
const out = schema.safeParse(null);
expect(out.success).toBe(false);
expect(out.error.issues.map(i => i.message)).toEqual(['foo']);