Error handling and boundary tests
Why it matters
A test suite that only verifies the happy path does not test the code that actually runs when things go wrong. ISO 25010:2011 §6.5.5 requires reliability verification to cover failure modes. Missing error-path tests mean: API 404 and 500 responses produce untested UI states that may crash or show stale data; invalid inputs reach business logic that was never verified to reject them correctly; catch blocks that silently swallow errors pass code review because no test ever triggered them.
Severity rationale
Low because untested error paths do not break current functionality but guarantee that failure scenarios — which happen in production — have never been automatically verified.
Remediation
Add at least one negative test per critical unit. A minimal pattern alongside each success test:
describe('fetchUser', () => {
it('returns user data on success', async () => {
mockFetch.mockResolvedValue({ id: '1', name: 'Alice' })
const user = await fetchUser('1')
expect(user.name).toBe('Alice')
})
it('throws UserNotFoundError when 404', async () => {
mockFetch.mockRejectedValue(new Response(null, { status: 404 }))
await expect(fetchUser('nonexistent')).rejects.toBeInstanceOf(UserNotFoundError)
})
it('handles null user id gracefully', async () => {
await expect(fetchUser(null as unknown as string))
.rejects.toThrow('User ID is required')
})
})
Aim for at least 20% of tests to verify failure scenarios, not just success paths.
Detection
-
ID:
error-handling -
Severity:
low -
What to look for: Examine the test suite (if it exists) for tests that verify error scenarios, not just success paths. Look for: tests that pass invalid input to functions and assert the error thrown; tests that simulate API failures (network errors, 4xx/5xx responses) and assert the UI shows an error state; React Error Boundary tests; tests for null/empty/boundary values (empty arrays, zero, empty string, undefined). Also check the production code for the presence of
try/catchblocks in async operations — missing error handling is a separate issue but correlated. A codebase with 100% "happy path" tests and no failure scenario tests is brittle. -
Pass criteria: Enumerate all relevant code locations. Tests exist that cover failure scenarios — invalid input, network errors, null/empty edge cases. Error boundaries (if React) are tested. At least 20% of tests verify failure paths.
-
Fail criteria: All tests only cover success scenarios; no tests assert error states, throws, or empty results.
-
Skip (N/A) when: No test files exist.
-
Detail on fail:
"All tests cover success paths only; no tests for invalid input, API failures, or null/empty edge cases" -
Remediation: Add negative tests alongside each unit test. A minimal pattern:
describe('fetchUser', () => { it('returns user data on success', async () => { mockFetch.mockResolvedValue({ id: '1', name: 'Alice' }) const user = await fetchUser('1') expect(user.name).toBe('Alice') }) it('throws a UserNotFoundError when 404', async () => { mockFetch.mockRejectedValue(new Response(null, { status: 404 })) await expect(fetchUser('nonexistent')).rejects.toBeInstanceOf(UserNotFoundError) }) it('handles null user id gracefully', async () => { await expect(fetchUser(null as unknown as string)) .rejects.toThrow('User ID is required') }) })
External references
- iso-25010:2011 · maintainability.testability — Maintainability — Testability
Taxons
History
- 2026-04-18·v1.0.0·Initial import from code-quality-essentials·automated