Testing Mobile Application on Unstable Internet
Mobile network is not stable channel. User enters subway: network exists, packets lost 30%. Switches WiFi to LTE: 2–3 seconds complete disconnection. Enters elevator: connection gone for minute. Most apps tested only on stable WiFi and look catastrophic on real traffic.
Simulation Tools
iOS: Network Link Conditioner
Standard tool on iOS — Network Link Conditioner from Xcode additional tools (Additional Tools for Xcode). Installed on simulator and real device via Settings → Developer.
Profiles: 3G, Edge, 100% Loss, Very Bad Network (5% loss, 500 ms delay). Can create custom: set Downlink Bandwidth, Uplink Bandwidth, Delay, Packet Loss.
Programmatic control for automated tests — via XCTest and XCTNSURLSessionTaskMetrics. Metrics give real connection conditions data in test.
Android: tc and Emulator Network Settings
On emulator: Emulator Extended Controls → Cellular → Network type → Edge / GSM and Signal strength → Poor.
Programmatic simulation via adb and tc (traffic control):
# Add 500ms delay and 20% packet loss
adb shell tc qdisc add dev wlan0 root netem delay 500ms loss 20%
# Reset
adb shell tc qdisc del dev wlan0 root
tc netem works at emulator network interface level. On real device needs root — for testing use emulator or test in real bad coverage zones.
Cross-Platform: Charles Proxy and Proxyman
Charles Proxy (macOS/Windows) and Proxyman (macOS) work as man-in-the-middle proxy. App configured to use proxy, then throttling applied:
Charles → Proxy → Throttle Settings: choose GPRS / 3G / Custom profile. All app requests go through proxy with simulated delay and limited bandwidth.
Plus — Breakpoints: intercept request, delay it 10 seconds manually, simulate timeout. Useful for checking UI while waiting.
What We Check
Timeout and retry logic. Request hung 30 seconds — what user sees? Spinning spinner with no cancel — bad. Cancel button + timeout + clear error message — good. Check every request type: feed load, form send, file upload.
Network switching. WiFi→LTE transition breaks TCP connections. Correct behavior: app detects switch via NWPathMonitor (iOS) / ConnectivityManager.NetworkCallback (Android) and reconnects. Incorrect: infinite spinner because old URLSession task hung.
// iOS: network switch monitoring
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
// have connection — retry pending requests
self.retryPendingRequests()
}
}
monitor.start(queue: DispatchQueue.global(qos: .background))
Packet loss. At 15–20% packet loss HTTP request may complete, may hang. Check: every request has timeout, timeout reasonable (5–15 seconds for data, 30–60 for file upload), retry implemented with exponential backoff.
Partial load. Data arrives in chunks on slow connection. If app shows content only after full response — user sees white screen 10 seconds. Streaming or progressive load improves perception.
Common Findings When Testing
Unclosed connections. URLSession tasks not canceled when controller disappears. Consequence: memory leak + request arrives 30 seconds later when screen already closed, app crashes with EXC_BAD_ACCESS.
Request duplication on retry. Retry button calls same method already executing. Result: two simultaneous requests, two responses, race condition. Fix: isLoading flag, disable button during request.
Data loss on interruption. User filled form, pressed Save, connection dropped. Data not saved — and lost. Right solution: local draft save before sending, auto-resend on network recovery.
Timeline
2–3 days — simulation tools setup, scenario testing by checklist, report with findings. Cost is calculated individually.







