Faster App Setup For Unit Tests

When Xcode runs your unit tests, it uses the App as a host for the tests. The tests only run after the App has finished launching. Loading core data, networking or other setup work you do from the App delegate slows down your unit tests. Here’s a quick tip to create a custom scheme to avoid unnecessary work when unit testing.

Reducing App Launch Time

What we want is to avoid doing the unnecessary setup when running our unit tests. To do that we can change our App target scheme to flag to our App delegate that we are unit testing. We have two options for passing a flag to our App:

  • Pass it a command-line argument.
  • Set an environment variable.

Whichever of these two approaches we choose we only want to do it when running our unit tests. Luckily the scheme editor let’s us do that without too much trouble.

Passing Launch Arguments

A quick recap on adding a launch argument:

  1. Edit the scheme for the app target. Product > Scheme > Edit Scheme (⌘<).
  2. Switch to the arguments tab of the test action.
  3. By default, the test action gets the same launch arguments and environment variables as the run action. We want a launch argument only when testing so uncheck the checkbox at the top of the dialog:
  4. Use the + button to add the argument:

Adding an argument to the test action

The ProcessInfo class has what we need to check for the launch argument in our App. The launch arguments are an Array of String that we can query for our unit test flag. For example:

var isUnitTesting: Bool {
  return ProcessInfo.processInfo.arguments.contains("-UNITTEST")
}

Setting Environment Variables

We can achieve the same effect using an environment variable if you prefer. The steps are as before except that you add an environment variable with a value:

Adding an environment variable to the test action

To test for the environment variable, we can again rely on ProcessInfo. This time we get a dictionary of environment variables:

var isUnitTesting: Bool {
  return ProcessInfo.processInfo.environment["UNITTEST"] == "1"
}

Avoid Unnecessary Setup

Either way, once we have a way of detecting we are unit testing we can avoid doing unnecessary setup work. In our App delegate or wherever you do your App setup:

// AppDelegate.swift
func application(_ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions:
  [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

  if !isUnitTesting {
    // Load Core Data
    // Setup dependencies
    // Other slow stuff..
  }
  return true
}

Note: You’ll want to keep your UI tests in a different scheme which does allow the full App setup.

Further Reading