iOS Testing Fundamentals
iOS testing requires understanding Apple’s tightly controlled ecosystem. Unlike Android where manufacturers can modify the OS, every iOS device runs Apple’s unmodified operating system. This consistency simplifies some testing aspects but introduces unique challenges around Apple’s strict guidelines and design patterns.
iOS App Lifecycle
Understanding the app lifecycle is critical for mobile testers because many bugs occur during state transitions.
App States
Not Running → Inactive → Active → Background → Suspended → Terminated
| State | Description | Testing Focus |
|---|---|---|
| Not Running | App has not been launched or was terminated | Cold start performance |
| Inactive | App is in foreground but not receiving events (e.g., incoming call overlay) | Interruption handling |
| Active | App is in foreground and receiving events | Normal functionality |
| Background | App is executing code but not visible | Background task completion |
| Suspended | App is in memory but not executing code | State restoration |
| Terminated | App is removed from memory | Data persistence |
Critical Test Scenarios
Cold start vs warm start: Time the app launch from terminated state (cold) versus suspended state (warm). Users notice if cold start takes more than 2 seconds.
Interruptions: Test what happens when:
- Phone call comes in during a critical operation
- Siri is activated
- Low battery alert appears
- Timer or alarm fires
- Control Center is opened
Background to foreground: After the app has been in the background for 30+ minutes, does it restore correctly? Check for:
- Expired auth tokens
- Stale data displays
- Lost form input
- Broken WebSocket connections
Memory pressure: iOS can terminate suspended apps at any time. Test state restoration after the app is killed by the system.
Xcode Testing Tools
XCUITest
XCUITest is Apple’s native UI testing framework. It interacts with the app through the accessibility system.
// Example XCUITest
func testLoginFlow() {
let app = XCUIApplication()
app.launch()
let emailField = app.textFields["Email"]
emailField.tap()
emailField.typeText("user@example.com")
let passwordField = app.secureTextFields["Password"]
passwordField.tap()
passwordField.typeText("password123")
app.buttons["Sign In"].tap()
XCTAssertTrue(app.staticTexts["Welcome"].waitForExistence(timeout: 5))
}
Key advantages:
- Runs on real devices and simulators
- Access to system UI elements (alerts, keyboards)
- Built into Xcode — no additional setup
- Supports accessibility identifiers for reliable element location
Key limitations:
- iOS only — cannot share tests with Android
- Slower execution compared to Espresso (Android equivalent)
- Limited gesture support for complex interactions
Xcode Instruments
Instruments is the profiling toolkit bundled with Xcode. Essential instruments for testers:
| Instrument | Purpose | When to Use |
|---|---|---|
| Time Profiler | CPU usage analysis | App feels sluggish |
| Allocations | Memory usage tracking | High memory consumption |
| Leaks | Memory leak detection | Memory grows over time |
| Energy Log | Battery consumption | Background battery drain |
| Network | Network request profiling | Slow data loading |
| Core Animation | Frame rate monitoring | Janky scrolling/animations |
| System Trace | Thread and process analysis | Deadlocks or freezes |
iOS Permission System
iOS has a strict permission model. Apps must request permission for sensitive resources, and the user can deny or revoke permissions at any time.
Permissions to Test
| Permission | First Request | After Denial | Reset Method |
|---|---|---|---|
| Camera | System dialog | Must go to Settings | Settings > Privacy > Camera |
| Location | 3 options: Once, While Using, Always | Settings only | Settings > Privacy > Location |
| Notifications | System dialog | Settings only | Settings > Notifications |
| Photos | System dialog with limited/full access | Settings only | Settings > Privacy > Photos |
| Microphone | System dialog | Settings only | Settings > Privacy > Microphone |
| Contacts | System dialog | Settings only | Settings > Privacy > Contacts |
Permission Testing Checklist
- First-time permission request shows correct dialog
- App handles permission denial gracefully (no crash, helpful message)
- App works correctly with limited photo access (iOS 14+)
- Location permission “While Using” vs “Always” behavior differs correctly
- Revoking permission in Settings mid-session does not crash the app
- App Tracking Transparency dialog (iOS 14.5+) appears before any tracking
App Store Review Preparation
Apple reviews every app submission. Common rejection reasons and how to test for them:
Top 5 Rejection Reasons
Bugs and crashes (most common)
- Test all flows end-to-end on the minimum supported iOS version
- Crash-free rate should be >99.5% before submission
- Test with no network, slow network, and airplane mode
Broken links and placeholder content
- Verify all URLs in the app are live and correct
- Remove any “Lorem ipsum” or “Coming Soon” placeholders
- Test all in-app links including terms, privacy policy, support
Incomplete metadata
- Screenshots must match current app UI
- Description must accurately reflect functionality
- Privacy policy URL must be accessible
Performance issues
- App launch must complete within reasonable time
- No excessive battery drain in background
- App size should be optimized (under 200MB for cellular download)
Design guideline violations
- Must use standard iOS navigation patterns or justify alternatives
- Must support latest screen sizes (including Dynamic Island)
- Must not mimic system UI elements
Pre-Submission Checklist
□ Tested on minimum supported iOS version
□ Tested on latest iOS version
□ All permission dialogs tested (granted and denied)
□ Dark Mode tested on all screens
□ Dynamic Type tested (largest and smallest sizes)
□ VoiceOver basic navigation works
□ No crashes in Xcode Organizer crash logs
□ All links functional
□ Privacy manifest updated (iOS 17+)
□ Screenshots match current UI
Exercise: iOS Bug Hunt
Scenario: You are testing a food ordering app on iOS. The app has been in development for 6 months and is preparing for its first App Store submission.
Identify potential bugs in each scenario:
- User adds items to cart, receives a phone call, and returns to the app 5 minutes later
- User grants location permission as “While Using App” but the app needs location for delivery tracking after the app is backgrounded
- User has Dynamic Type set to the largest accessibility size
Solution
Background state restoration: The cart data might be lost if the app was suspended and terminated during the call. Check: cart items still present, prices still current (not stale cached data), any active timer (delivery countdown) correctly resumed.
Location permission mismatch: With “While Using” permission, the app loses location access when backgrounded. The delivery tracking will fail. The app should: (a) Request “Always” permission when delivery tracking is activated, or (b) Clearly explain to the user why “Always” permission is needed, or (c) Use alternative tracking that works without background location.
Dynamic Type overflow: Large text sizes frequently cause: text truncation, overlapping labels, buttons too small to tap, horizontal scrolling where content should wrap, price labels cut off. The entire checkout flow should be tested with the largest Dynamic Type size.
Pro Tips from Production Experience
Tip 1: Test on the oldest supported iOS version first. Most crashes occur on older iOS versions where deprecated APIs might behave differently. If you support iOS 15+, run your critical path tests on iOS 15 before testing on iOS 17.
Tip 2: Use Xcode Organizer for real-world crash data. After beta testing with TestFlight, Xcode Organizer shows real crash reports from testers. Review these before App Store submission — they often reveal device-specific crashes you missed.
Tip 3: Test the “Not Determined” permission state. iOS only shows the permission dialog once. After that, the user must go to Settings. Test what your app does when permission has never been requested (first launch) versus when it has been explicitly denied.
Key Takeaways
- iOS app lifecycle transitions are a common source of bugs — test interruptions, background suspension, and state restoration
- XCUITest is the native iOS UI automation framework — use accessibility identifiers for reliable tests
- Xcode Instruments is essential for performance and memory profiling
- Permission testing must cover first request, denial, revocation, and limited access scenarios
- App Store rejections are usually caused by bugs, incomplete features, or design guideline violations