Your app looks perfect on office wifi. Every screen paints instantly, requests come back in tens of milliseconds, and the loading states you wrote flash by too fast to notice. Then a user on a train, a spotty LTE connection, or a hotel network hits a three-second request, a timeout, or a dropped call — and the UI you never saw under stress falls apart. A spinner that never stops. A screen stuck half-populated. An error toast that fires for a case you assumed could not happen.
Loading spinners, timeouts, retries, and error branches only run under a slow or failing network — exactly the condition your fast dev connection will not reproduce. So those code paths ship untested. The outcome of this guide is a repeatable way to force that condition on demand: on macOS, route your traffic through Rockxy, then throttle it, add latency to one endpoint, or make a specific request hard-fail — and watch your UI actually react.
Why you cannot test this without shaping traffic
A slow-network bug is a timing bug. Your loading state is correct only if it appears, holds, and clears in the right order; your timeout handler is correct only if a request actually takes longer than the timeout; your error branch is correct only if a request actually fails. On a fast connection none of those preconditions occur, so the code never executes and its bugs stay invisible until a real user finds them.
The reliable way to create those preconditions is to shape the traffic between your client and the server as it passes through a proxy. Rockxy is a local proxy on your Mac: once your client's requests flow through it, Rockxy can slow them down, hold them, or fail them — without you touching the server, the client code, or your actual internet connection. Three tools cover the common cases:
- Network Conditions throttles and delays traffic across the session, to imitate a generally slow link.
- Map Local with a Delay Response preset adds latency to one specific endpoint, so you can make a single call slow while everything else stays fast.
- Block List makes a matching request hard-fail, so you can drive the error branch on demand.
What you will need
- Rockxy on macOS 14 (Sonoma) or later. It is a native app; download it from the Rockxy download page.
- Your app or client already routing traffic through Rockxy, with rows appearing in the capture list. If nothing shows up — a common first-hour surprise with local services — start with Why Localhost Traffic Isn't Showing in Your Proxy and come back once you can see requests.
- A UI or client with loading, timeout, and error states you want to exercise — a request whose spinner, retry, and failure paths you can watch.
- For HTTPS endpoints, the host inside Rockxy's SSL Proxying scope with the certificate trusted, so Rockxy can act on the real request rather than an opaque tunnel.
Step 1 — Confirm the request is captured first
Before shaping anything, prove that the request you care about actually appears in Rockxy. Trigger the action in your app — load the screen, tap the button, run the client — and find its row in the traffic list. Select it and confirm you can read the method, URL, status, and timing.
What you should see in Rockxy: a row for your endpoint with a normal, fast timing — the baseline you are about to disrupt. If the request does not show up at all, no amount of throttling will help, because Rockxy can only shape a request that passes through it. Fix capture first, then continue.
Step 2 — Throttle the whole session with Network Conditions
To imitate a generally slow link — the "everything is sluggish" case — use Network Conditions. It applies bandwidth throttling and added latency to traffic moving through the proxy, so your requests behave as if the whole connection were degraded. Enable it, choose a slow profile, and re-run the same action from Step 1.
The exact control labels and available profiles live in the app's Network Conditions panel and the Network Conditions documentation — set a slow bandwidth and higher latency, then apply it. The point is not a specific number; it is that every request now takes meaningfully longer, which is precisely when your loading states have to hold and your timeouts have to fire.
What you should see in Rockxy: the same endpoint's row now reports a much longer time than the baseline from Step 1. In your app, the spinner should appear and stay visible for the duration; if your client has a timeout shorter than the delay, the request should fail and your timeout handler should run. Both outcomes are the ones you could not trigger on a fast connection.
Step 3 — Delay one endpoint with Map Local
Session-wide throttling is a blunt instrument. Often you want to make one call slow — the dashboard fetch, say — while every other request stays fast, so you can isolate how a single laggy endpoint affects the screen. Map Local does this with its Delay Response presets.
Create a Map Local rule that matches the endpoint's URL, then set its response delay from the built-in presets. The available options are No Delay, 1s, 2s, 3s, 5s, 10s, 30s, 60s, Random 1–15s, or a Custom value from 0 to 300 seconds. Pick a delay that is longer than your client's timeout if you want to test the timeout path, or shorter if you want to test the "slow but succeeds" loading path. The Random 1–15s preset is useful for reproducing jittery, inconsistent latency rather than a fixed lag.
If you are new to Map Local, the mechanics of matching a request and serving a controlled response are covered in How to Mock an API Response with Map Local on macOS. Here you are using the same rule for its timing knob rather than its body — the delay applies to the matched response only.
What you should see in Rockxy: only the matched endpoint's row shows the added latency; sibling requests stay fast. In your UI, this is where partial-render bugs surface — a screen that shows some data immediately but leaves the delayed section spinning, or a layout that jumps when the slow response finally lands.
Step 4 — Force a failure with Block List
A slow request is one failure mode. A request that never completes is another, and it drives a completely different branch of your code: the catch block, the error toast, the retry button, the offline banner. To trigger it deterministically, use Block List to block or fail a matching request.
Add a Block List entry that matches the endpoint you want to break, so the request is refused instead of reaching the server. Now re-run the action. Because the request hard-fails, your client sees an error rather than a slow success — the exact input your error-handling code was written for but rarely receives.
This is deterministic in a way that pulling your wifi is not: you fail one specific endpoint on demand, repeatably, while the rest of the app keeps working. That lets you answer questions a flaky real network cannot — does the retry actually re-send, does the error state clear when the next call succeeds, does a failed background call corrupt the screen.
What you should see in Rockxy: the matched request appears as a failed row rather than a successful one, and your app shows its error state. Blocked and failed transactions stay visible in the list, so you can confirm the failure was injected on purpose and see exactly which call your UI reacted to.
Putting it together: a slow-network test pass
Used in sequence, these three tools cover the states that only appear under a bad connection. Throttle the whole session to check that nothing breaks when everything is slow. Delay a single endpoint to check partial-render and per-request timeout behavior. Block one call to check the error and retry path. Run each while watching both the Rockxy row and your UI, and you have exercised the loading, timeout, and failure branches without waiting for a real user to hit them in production.
Because every change is a rule inside Rockxy rather than a change to your code, you can toggle each condition on and off between runs and compare. Once a failing state is captured, deeper per-request debugging — inspecting exactly what the client sent and how it reacted — becomes a matter of reading the captured flow rather than guessing.
If it does not work
The delay or block has no effect. The request is not passing through Rockxy, so there is nothing to shape. Confirm the row appears in the capture list first (Step 1). A client that bypasses the proxy — a hardcoded direct connection, a loopback shortcut, or a tool that ignores proxy settings — is unaffected by any of these rules until you route it through the proxy.
Your rule does not match the request. The endpoint you see failing in the app is not the one your Map Local or Block List entry targets. Compare the exact URL in the captured row against your rule pattern; a path, port, or query difference is enough to miss.
The HTTPS request shows as a tunnel, not a shaped request. For Rockxy to act on an HTTPS endpoint, the host must be inside SSL Proxying scope and the certificate trusted. Without that you get an opaque tunnel row and the rules cannot apply. If the host pins its certificate, Rockxy will not silently defeat that — a pinned or failed handshake is surfaced as a failed transaction in the list rather than being decrypted.
The UI still looks fine under load. That may be correct — but confirm the request was actually slow or failed by reading its Rockxy row. A common trap is a client timeout longer than your delay, so the request succeeds before the timeout path can run; shorten the timeout or lengthen the delay to force the branch you want.
Limitations
These tools shape traffic that flows through the proxy. A client that bypasses the proxy — by design or by misconfiguration — will not see the throttle, delay, or block; route it through Rockxy first. HTTPS endpoints still require SSL Proxying scope and certificate trust, and pinned hosts appear as failed transactions rather than decrypted, shaped requests. Rockxy is a native macOS app, so this workflow runs on your Mac against the traffic your Mac's clients send. And these are network-layer conditions: they change timing and success at the request boundary, not the server's own behavior under load.
Next steps
Slow and failing networks are not edge cases you can defer — they are the normal operating condition for a lot of your users, and the branches they trigger are the ones least likely to have been exercised before release. Shaping traffic through Rockxy turns "I hope the loading state works" into a repeatable check you run before shipping.
From here, read the Network Conditions documentation for the full set of throttle and delay controls, use Map Local to combine a delayed response with a mocked body for a fully controlled failure, and if your requests are not showing up in the first place, fix capture with the localhost routing guide. When you are ready to shape traffic against your own stack, download Rockxy and start with one throttled request.