Testing cheat sheet
The 8 Testing interview answers in 30 seconds each, plus quick facts. Printable, free.
One page per topic: print it or save it as PDF from your browser's print menu. Practise these questions
Writing good unit tests with XCTest
Subclass XCTestCase and write test… methods that arrange inputs, act once and assert the outcome with XCTAssert functions. Create fresh objects per test in setUp or inside the test so tests are independent and can run in any order or in parallel. Inject dependencies so tests are fast, deterministic and offline.
Testing async code
Mark the test async (and throws) and await the code directly; XCTest and Swift Testing both support it. For callback or delegate APIs, use XCTestExpectation with await fulfillment(of:timeout:), or confirmation in Swift Testing. Test main-actor types from @MainActor tests, and control time and dependencies instead of sleeping.
Test-driven development
Test-driven development is a short loop: write a failing test for the next small behaviour, write just enough code to pass it, then refactor while the tests stay green. It works best for logic with clear inputs and outputs, such as parsers, pricing rules and view models, and less well for exploratory UI work.
Test doubles: stubs, spies, mocks and fakes
Test doubles stand in for real dependencies. A dummy fills a parameter and is never used. A stub returns canned answers. A spy records how it was called so you can assert on it. A mock is set up with expectations and verifies them. A fake is a working lightweight implementation, such as an in-memory store. In Swift you build them from small protocols.
UI tests with XCUITest
Launch XCUIApplication with launch arguments that switch on stubbed data, find elements by stable accessibilityIdentifier, interact with them, and wait with waitForExistence(timeout:) rather than sleeps. Keep UI tests few and focused on critical flows, and use page objects to keep them readable.
Swift Testing
Swift Testing is the newer testing framework that ships with Swift 6 and Xcode 16. Tests are functions marked @Test, grouped in @Suite types, and checked with #expect and #require. It runs tests in parallel by default, supports parameterized tests with arguments:, and adds traits like tags, time limits and conditions. It can live alongside XCTest in the same target.
Testing concurrent code without sleeps
Make time and scheduling dependencies. Inject a Clock so tests can use a controllable clock, await the real work or its returned Task, and use confirmations or AsyncStream continuations to signal events. Run tests many times and with Thread Sanitizer to catch races, rather than adding longer sleeps.
@MainActor @Observable
final class FeedModel {
private let clock: any Clock<Duration>
init(clock: any Clock<Duration> = ContinuousClock()) { self.clock = clock }
func autoRefresh() async {
while !Task.isCancelled {
await refresh()
try? await clock.sleep(for: .seconds(30))
}
}
func refresh() async { /* load */ }
}Debugging with LLDB
At a breakpoint, v (frame variable) reads variables straight from memory without running code, so it is fast and safe. p evaluates an expression and po evaluates it and prints its description. Use breakpoint conditions, actions that log and continue, symbolic breakpoints like UIViewAlertForUnsatisfiableConstraints, and watchpoints to catch when a value changes.
Quick facts
- Test plans
- Xcode test plans run the same tests under different configurations, such as languages, sanitizers or environment variables.
- XCTest performance tests
measure(metrics:)records metrics such as clock time, memory and CPU across runs.- Parameterized tests
- Swift Testing's
@Test(arguments:)runs one test function for each input.