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
StateDescriptionTesting Focus
Not RunningApp has not been launched or was terminatedCold start performance
InactiveApp is in foreground but not receiving events (e.g., incoming call overlay)Interruption handling
ActiveApp is in foreground and receiving eventsNormal functionality
BackgroundApp is executing code but not visibleBackground task completion
SuspendedApp is in memory but not executing codeState restoration
TerminatedApp is removed from memoryData persistence

Critical Test Scenarios

  1. 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.

  2. 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
  3. 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
  4. 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:

InstrumentPurposeWhen to Use
Time ProfilerCPU usage analysisApp feels sluggish
AllocationsMemory usage trackingHigh memory consumption
LeaksMemory leak detectionMemory grows over time
Energy LogBattery consumptionBackground battery drain
NetworkNetwork request profilingSlow data loading
Core AnimationFrame rate monitoringJanky scrolling/animations
System TraceThread and process analysisDeadlocks 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

PermissionFirst RequestAfter DenialReset Method
CameraSystem dialogMust go to SettingsSettings > Privacy > Camera
Location3 options: Once, While Using, AlwaysSettings onlySettings > Privacy > Location
NotificationsSystem dialogSettings onlySettings > Notifications
PhotosSystem dialog with limited/full accessSettings onlySettings > Privacy > Photos
MicrophoneSystem dialogSettings onlySettings > Privacy > Microphone
ContactsSystem dialogSettings onlySettings > 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

  1. 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
  2. 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
  3. Incomplete metadata

    • Screenshots must match current app UI
    • Description must accurately reflect functionality
    • Privacy policy URL must be accessible
  4. 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)
  5. 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:

  1. User adds items to cart, receives a phone call, and returns to the app 5 minutes later
  2. User grants location permission as “While Using App” but the app needs location for delivery tracking after the app is backgrounded
  3. User has Dynamic Type set to the largest accessibility size
Solution
  1. 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.

  2. 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.

  3. 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