XCTest NSURLSession Stall on main thread
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
NSURLSession tests can appear to stall when the test blocks the main thread or waits incorrectly for asynchronous callbacks. XCTest is designed to coordinate async work through expectations or async test functions, not through manual run-loop blocking. This guide shows patterns that keep networking tests deterministic and fast.
Why Main-Thread Stalls Happen
A common anti-pattern is waiting with semaphores on the main thread while URLSession callbacks also need main-thread execution context. This creates a deadlock-like situation where the callback cannot run, so the test never finishes.
Instead, use XCTest expectations or async test methods.
Correct XCTestExpectation Pattern
Expectations make callback completion explicit and integrate with XCTest timeouts.
This keeps the test runner responsive and provides clear failure messages on timeout.
Modern Async Test Functions
On modern Swift and XCTest versions, async test functions are cleaner.
This avoids manual expectation bookkeeping and reads like synchronous code.
Isolate Networking for Reliable Unit Tests
For true unit tests, avoid real network calls and inject a custom URLProtocol mock. This removes flakiness from external services.
Use this with a dedicated URLSessionConfiguration in tests for predictable outcomes.
Test Delegate-Based Sessions Safely
If your production networking layer uses URLSessionDelegate, keep tests asynchronous and isolate delegate callbacks with dedicated expectations.
This keeps callback delivery observable without blocking the thread that drives test execution.
Keep Integration Tests Separate
Network integration tests and unit tests should not share the same reliability expectations. Mark slow network tests clearly and run them in a dedicated pipeline stage. Fast deterministic unit tests should rely on mocks, while integration tests verify endpoint contracts and authentication behavior.
Common Pitfalls
The biggest pitfall is mixing asynchronous APIs with synchronous waiting primitives on the test thread. Another issue is setting timeouts too low for CI machines, causing random failures even when code is correct. Teams also often leave real network dependencies in unit tests, which introduces nondeterministic failures from DNS, latency, or remote outages. Finally, avoid fulfilling expectations multiple times from retry logic unless the test is designed for it. XCTest will flag over-fulfillment as a failure.
Summary
- Do not block the main test thread with semaphores for URLSession callbacks.
- Use XCTest expectations or async test functions for network completion.
- Prefer mocked networking for deterministic unit tests.
- Set realistic timeouts and clear assertions for CI stability.
- Keep async test flow explicit to prevent hidden deadlocks.
Related reading
- xunit Assert.ThrowsAsync does not work properly?
- You Don't Know JS Async and Performance - cooperative concurrency?
- ZeroMQ How to handle non-message-related, asynchronous events in a ZeroMQ node?
- ZeroMQ Publish and Subscribe concurrently
- XCTest/XCTest.h not found on old projects built in Xcode 6
- You have not accepted the license agreements of the following SDK components
- ZeroMQ Recommended pattern for inproc clients from multiple threads pushing ordered messages to a server?
- 1 thread vs 5 threads for distributed system communications?
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.