Frontend teams are usually pretty good at testing the happy path. The API returns 200. The response shape is correct. The list has data. The network is fast enough. The user sees the page we designed.
Great.
But products do not live only in the happy path.
They live in all the awkward states around it:
- the endpoint returns 500
- the list is empty
- the request is slow
- the response is malformed
- the token expired
- the user has no permissions
- staging data changed again
- the backend endpoint is not ready yet
These states are often where the UI becomes the most important. They are also the states that are hardest to reproduce on demand.
The problem with relying on staging
A lot of teams treat staging as the place where frontend edge cases should be tested. In theory, that makes sense. In practice, staging is rarely in the exact state you need.
- Maybe you need an empty list, but staging has 400 items.
- Maybe you need the API to return a 500, but the backend is currently healthy.
- Maybe you need a slow response to test loading behavior, but the request is too fast.
- Maybe you need a specific permission error, but nobody wants to mutate test data or add a temporary backend flag just so you can check one UI state.
So the frontend workaround begins.
Common workarounds
There are a few common ways to deal with this.
1. Mock servers
Mock servers are powerful. They are great when you need shared contracts, stable fake environments, or a full backend replacement during development. But they can also be heavy when the task is small.
Sometimes you do not need a full mock environment. You just need this one request to return an empty array for ten minutes while you fix a UI. Setting up a mock server for that can feel like too much ceremony.
2. Local fixtures
Local fixtures are fast and simple.
You create a JSON file, import it somewhere, and render the UI against that data.
This works well for isolated components. But it gets harder when the behavior depends on the actual network flow of the app: auth, routing, loading states, retries, request timing, headers, or runtime conditions.
Fixtures can also drift from real API responses over time.
3. Backend flags or test endpoints
Sometimes the backend team can add a flag, seed special data, or expose a test endpoint. That can be useful, especially in mature teams. But it also creates coordination overhead. The frontend developer has to ask for the state, wait for it, and hope it still exists when QA or design tries to reproduce the same thing later.
4. Browser hacks
We have all done some version of this. Add a temporary throw. Edit code in DevTools. Comment out a fetch call. Hardcode a response. Change a local branch just to force an error state.
It works, but it is fragile. It is also easy to forget, hard to share, and not something you want to rely on for repeatable testing.
A lighter workflow: override the response locally
For many frontend tasks, the ideal workflow is smaller:
- Open the app.
- See the real request the app is making.
- Choose the request you want to control.
- Return the response you need.
- Reload and test the UI.
No backend change. No proxy setup. No full mock server. No app code changes.
Just one local override for one real browser request.
That is the workflow I have been thinking about while building Ruse.
Example states worth testing
Here are a few states I think every production UI should be easy to test.
Empty list
{
"items": []
}
Does the UI show a thoughtful empty state? Is the CTA correct? Does the layout collapse awkwardly? Does pagination disappear?
Server error
{
"error": "Internal server error"
}
Does the page recover? Can the user retry? Is the message useful? Does the app accidentally show a blank screen?
Slow response
A slow API often reveals problems that fast local development hides.
Do loading indicators appear? Do buttons stay disabled? Does the page jump when data arrives? Can the user trigger duplicate actions?
Malformed response
{
"items": null
}
This is where defensive UI code gets tested.
Does the app fail gracefully, or does one unexpected value take down the entire screen?
Permission error
{
"error": "You do not have access to this resource"
}
Permission states are product states too. They need design, copy, and behavior.
Why browser-level mocking is useful
Mocking at the browser level is interesting because it starts from what your app is actually doing.
You are not inventing a request from memory. You are not manually recreating the URL, method, and response shape from scratch. You are looking at real traffic and deciding: "for this request, return this instead."
That makes the loop feel more natural for frontend work.
It is especially useful when you are:
- building UI before the backend is complete
- testing error states
- preparing stable demos
- reproducing QA bugs
- checking loading behavior
- designing empty states
- working against flaky staging data
Where Ruse fits
I am building Ruse as a Chrome extension for this workflow.
The idea is simple:
- inspect browser requests
- create local mock rules
- return custom responses
- test the UI state you need
- keep everything local in the browser The first release is intentionally small and local-first. It is not trying to be a full API platform. It is focused on making the first useful mock fast.
Ruse is currently in alpha and going through Chrome Web Store review.
If this workflow sounds useful, I am looking for early testers:
https://ruse.dev/support?topic=early-access
Final thought
Frontend edge cases should not require a backend meeting.
Sometimes you need a full mock server. Sometimes you need contract testing. Sometimes you need proper seeded environments.
But sometimes you just need one request to return one different response so you can finish the UI in front of you.
That small loop is worth making better.





















