Accurate iOS code coverage

The natural next step from testing your code, perhaps even in a test-first manner, is knowing how much of your code is covered by these tests. To do that, you have to first open the Edit Scheme and tell Xcode to Gather coverage data.1

Gather coverage data setting in Xcode
Gather coverage data setting in Xcode

Running unit tests as is would give you inaccurate coverage, as it would take into account AppDelegate and everything it touches.2 This is easily seen on a new project, using the Single View Application template that comes with AppDelegate.swift and ViewController.swift files. As you see below, I've also added AnIntegration.swift that contains only one empty function which gets called from AppDelegate and so, by definition, has a 100% coverage:

Inaccurate coverage
Inaccurate coverage

To get accurate code coverage first add a AppDelegate barebones replica to your test-target:

import UIKit

class TestAppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    return true
  }
}

Then add main.swift to the app-target in and finally tell Xcode to, when testing, replace AppDelegate with the above TestAppDelegate:

import Foundation
import UIKit

let argc = CommandLine.argc
let argv = UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(argc))
let appDelegateClass: AnyClass = ProcessInfo.isUnitTesting
  ? NSClassFromString("MyAppTests.TestAppDelegate")!
  : AppDelegate.self

UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass))

The ProcessInfo.isUnitTesting above is an extension on ProcessInfo:

import Foundation

extension ProcessInfo {
  static var isUnitTesting: Bool {
    return processInfo.environment["XCTestConfigurationFilePath"] != nil
  }
}

Since you supplied main.swift file, you now also have to remove the @UIApplicationMain attribute that's right above your AppDelegate:

@UIApplicationMain // remove thisclass AppDelegate: UIResponder, UIApplicationDelegate {

Finally, run the tests again and coverage of AnIntegration is now at the actual value of 0%:

Accurate coverage with a test AppDelegate
Accurate coverage with a test AppDelegate


  1. This setting is currently (Xcode 8.2.1) disabled by default.

  2. Because app process hosts these unit tests and they are executed after receiving notification for UIApplicationDidFinishLaunching.