Most frontends have a confident happy path and a fragile everything else. The 401 retry loop has never been seen by the dev who wrote it. The 500 toast renders raw HTML. The 429 case throws an unhandled promise. None of these are caught in review because none of them are easy to trigger.
The reason error handling rots is structural: forcing a real backend to return a 500 on demand is annoying, and forcing it to return a specific 500 with a specific body is annoying enough that nobody does it twice. Mockfill removes that friction entirely.
Nano Banana prompt: "UI mockup of a web app dashboard with an error toast in the top right corner. The toast contains raw, leaked stack trace text like 'NullPointerException at UserController.java:142'. A bold red highlight box and arrow point at the toast. Caption strip: 'What happens when the 500 path is never tested.' Light theme, modern flat design, indigo accents, sharp readable typography."
The pattern: one endpoint, many rules
For every endpoint that matters, create a small family of rules — one per status code you care about — pointing at the same URL. Mockfill matches by method + URL pattern with first-match-wins ordering, so you enable exactly one at a time.
A typical family for GET /api/users/:id:
| Rule name | Status | Body |
|---|---|---|
users_200_happy |
200 | Realistic user object |
users_200_empty |
200 | {} or null fields |
users_401_expired |
401 | { "error": "AUTH_EXPIRED" } |
users_403_forbidden |
403 | { "error": "FORBIDDEN" } |
users_404_missing |
404 | { "error": "NOT_FOUND" } |
users_429_throttled |
429 | { "error": "RATE_LIMITED" } with retry-after header |
users_500_unknown |
500 | Realistic server error body |
Nano Banana prompt: "UI mockup of a Mockfill rules list panel, light theme. Seven stacked rule cards each labeled 'users_200_happy', 'users_200_empty', 'users_401_expired', 'users_403_forbidden', 'users_404_missing', 'users_429_throttled', 'users_500_unknown'. Each row shows a method tag (GET), a URL '/api/users/:id', and a toggle switch on the right. Status codes are color-coded badges. Caption: 'One endpoint, seven testable realities.' Modern flat SaaS design, indigo accents."
Group them into presets
Individual rules are not the unit of work — scenarios are. Mockfill presets let you flip a whole set of rules at once. Build presets that match the user-visible scenarios you care about:
- Happy path — all 200s
- Auth failure — every protected endpoint returns 401
- Server outage — every endpoint returns 500
- Throttled — every endpoint returns 429 with
retry-after: 5 - Half outage — read endpoints return 200, write endpoints return 503
A preset is a one-click context switch. QA can run the entire flow under "Auth failure" in 30 seconds, then switch to "Throttled" and run it again.
A realistic 401 body
{
"error": {
"code": "AUTH_EXPIRED",
"message": "Your session has expired. Please sign in again."
}
}
A realistic 429 with the right header:
{
"status": 429,
"headers": { "retry-after": "5" },
"body": { "error": "RATE_LIMITED" }
}
The value is in the specificity. Mocking a generic 500 with {} does not catch the bug where your error toast tries to render error.message and crashes on undefined. Mocking the exact body shape your backend returns does.
Nano Banana prompt: "Two side-by-side UI mockups of an error toast notification. Left toast displays the literal text 'undefined' with a red broken-icon. Right toast displays a polished message 'Your session has expired. Please sign in again.' with a key icon. Caption strip: 'Same code, different mock body.' Light theme, indigo accents, modern flat design, crisp readable typography."
What to actually verify in each scenario
For each preset, walk the UI and answer:
- 401: Does the app redirect to login? Does it preserve the in-progress form state? Does it clear cached user data?
- 403: Is the messaging distinguishable from "not found" without leaking object existence?
- 404: Does the empty state look intentional or broken?
- 429: Does the UI surface
retry-after, or does it spam retries? - 500: Is the error message generic enough that it does not leak internals (stack traces, file paths, framework names)?
The 500 check is also a small but real security check. Improper error handling that leaks server internals is a recognized OWASP issue, and the cheapest way to catch it is to mock a verbose error and look at what your UI actually renders.
Sharing with QA
Export the rule set as JSON, commit it to your repo, and write a one-paragraph README:
repo/
tools/
mockfill/
error-states.json
README.md
Now QA does not need a backend dev to "make staging return a 500." They import the JSON, pick a preset, and run the test. The back-and-forth disappears.
Nano Banana prompt: "UI mockup of a dropdown menu inside a Mockfill extension panel. The dropdown is open and shows five preset options stacked: 'Happy path', 'Auth failure', 'Server outage', 'Throttled', 'Half outage'. Each option has a small colored status icon. Caption: 'QA's regression menu.' Light theme, modern flat SaaS design, indigo highlights, soft drop shadow on the dropdown."
Graduating to automation
Once a scenario catches a real bug, it deserves to be enforced in CI. Port the same response body and status into a Cypress cy.intercept or a Playwright route.fulfill. The mock you used to find the bug becomes the mock that prevents it from coming back.
The takeaway
Error handling rots when it is hard to test. Mockfill makes it cheap. Build the rule family once, build the presets once, share the JSON, and the 500 path stops being scary.



