SimMonkey
SimMonkey

For iOS developers · debug builds only

Every response your backend won't give you.

SimMonkey docks beside the iOS Simulator and puts each request your app makes under your control. Stub it, fail it, slow it down, send it somewhere else — and the next request the app makes gets exactly that. No rebuild. No relaunch. Nothing to install in the simulator.

$ [simmonkey] 1.0.0 attached — panel at 127.0.0.1:8377

The SimMonkey panel docked beside an iPhone simulator window, showing live traffic
Docked to the simulator. Drag the simulator; the panel follows.

The problem

The screens that break in production are the ones you can't reach in development.

The backend is up, so you never see the offline state. It's fast, so the skeleton never shows. It returns well-formed data, so the parser never meets the field that's missing.

You can mock it, and rebuild for every scenario you thought of in advance. You can proxy it, after installing a certificate in the simulator and doing it again after every reset. Or you can ask the backend team for a broken endpoint on staging, and wait.

How it works

Three steps, in this order. The order is the only part that matters.

  1. 1

    Add two lines to your app.

    #if DEBUG
    SimMonkey.start()
    #endif

    In your App initialiser, before anything creates a URLSession. Keep the guard: the package has no business in a release build, and that's what keeps it out.

  2. 2

    Run the panel.

    It finds the simulator window, docks beside it, and follows when you drag. From the moment your app launches, every request it makes appears in the panel as it happens — method, path, status, timing.

  3. 3

    Click a request. Write a rule.

    Pick a status, paste a body, add a delay, or hand back a real NSURLError. Save. The next matching request gets it. The app never restarts, and you never touched the backend.

What it does

Six things you'll reach for. Each one is a rule or a click, never a rebuild.

Traffic

Every request, with everything.

Headers, cookies, both bodies — including the Cookie header URLSession adds below the level your code can see. Click a row and the panel splits: request on the left, response on the right.

The Traffic tab with a request selected and its request and response shown side by side
The rule editor with a URL pattern, a JSON body pattern, and a stubbed response

Rules

Rules that understand your API.

Match on the URL — or on the request body too, for the service that answers every operation from one endpoint and only a field in the JSON tells them apart. Paste the fields that matter:

{ "gridName": "ForeignPaymentOrderCodebookFlat" }

Order, whitespace and every other field are ignored. First matching rule wins; drag to reorder.

Failure & latency

Break it for real.

A rule can hand your app a genuine NSURLErrorNotConnectedToInternet, not a mocked-out success — so it's your real error path on screen. A three-second delay holds the loading state long enough to actually look at it. Cancel mid-delay and nothing arrives afterwards.

Offline on the station list fail
method
GET
pattern
/radio/rs-stations\.json
failCode
-1009
delayMs
3000
A large JSON response shown as a collapsible tree in the detail pane

Inspection

Big payloads, readable.

JSON renders as a collapsible tree that opens the parts worth opening. A 434 KB station list arrives as 401 rows, not fifteen thousand. Bodies that aren't what their Content-Type claims still show — decoded, and labelled so you know your server is lying.

Forward

Don't fake it. Reroute it.

Send matching requests on to a real server with the URL rewritten — a whole path moved to staging with $1 — and a test token injected. Status and body come back from the server, not from you.

Edit & Send

Replay anything, changed.

Take any captured request, edit the body, press ⌘↩. The response lands beside it. Cookies go out exactly as captured, so an authenticated replay stays in the session.

The Edit and Send window with an edited request on the left and the server's response on the right

The guarantee

Close the panel and nothing happened.

Every failure inside the bridge — panel not running, port refused, panel gone mid-session — resolves to the same thing: pass the request through untouched, in a few milliseconds. A stopped tool never looks like a broken app.

Nothing to install in the simulator, nothing in your entitlements, no permission prompt on the Mac. Forwarded requests share the app's cookie jar, so a session established at login keeps working with the panel attached.

What it won't do

It sees URLSession. That's the deal.

URLSession.shared, custom sessions, Alamofire, AsyncImage — even progressive AVPlayer streams, which pass through and play.

  • It doesn't see WKWebView content — separate process.
  • It doesn't see Network.framework or raw sockets.
  • It doesn't see HLS, or anything the system loads out of process.
  • It's simulator-only; the panel is reached over loopback.
  • It's debug-only, by design. Keep the #if DEBUG.

Get it

The panel

SimMonkey.app · macOS 14+ · Apple Silicon

Download

Unzip. Double-click. It finds the simulator.

The package

SimMonkeyKit · iOS 15+ · Swift Package

SimMonkeyKit on GitHub

Add as a package. Never copy the source — copies drift.