A common pattern now for a while is to limit what happens in your AppDelegate when you are running your unit tests. Otherwise you will be starting up your application while unit tests are trying to run, causing plenty of weird failures and hard to debug errors.

One way (there are a bunch!) of doing this is to just look to see if the XCTest class is available:

private func areTestsRunning() -> Bool {
  return NSClassFromString("XCTest") != nil
}

Then in your AppDelegate, put this check near the top and just exit before starting any of the apps services or UI.

Now we have a new class in our apps named SceneDelegate that implements the UIWindowSceneDelegate protocol and allows your app to enable support for multiple windows. We need to make the same check above for unit tests so we prevent things from starting here as well.

For example in a new SwiftUI project, here is the template code:


class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

    func sceneDidDisconnect(_ scene: UIScene) {
        // Called as the scene is being released by the system.
        // This occurs shortly after the scene enters the background, or when its session is discarded.
        // Release any resources associated with this scene that can be re-created the next time the scene connects.
        // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // Called when the scene will move from an active state to an inactive state.
        // This may occur due to temporary interruptions (ex. an incoming phone call).
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // Called as the scene transitions from the background to the foreground.
        // Use this method to undo the changes made on entering the background.
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // Called as the scene transitions from the foreground to the background.
        // Use this method to save data, release shared resources, and store enough scene-specific state information
        // to restore the scene back to its current state.
    }


}

When running our unit tests, we don’t want this ContentView created or any services starting. So we can add our check similar to the below:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

  // Use a UIHostingController as window root view controller.
  if let windowScene = scene as? UIWindowScene {
      let window = UIWindow(windowScene: windowScene)
      if !areTestsRunning() {
        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()
        window.rootViewController = UIHostingController(rootView: contentView)
      }
      self.window = window
      window.makeKeyAndVisible()
  }
}

I still let the window be created in case the tests want to use it. But I don’t add any view controllers to it. And for SwiftUI projects I don’t add any Environment objects. Now our unit tests can run without any additional processing happening while they are running.

Recently I ran into another situation where we need to put this kind of protection for SwiftUI apps. I was having some very weird issues with previews not working. Debugging these kinds of issues are extremely difficult so I usually revert to old school techniques like commenting out code until the crash stops. This led me on a journey and I discovered something very interesting. When Xcode generates previews of a View, the SceneDelegate runs as normal! Sure enough, I had some services starting in the SceneDelegate that I really didn’t want when generating previews. When my SceneDelegate starts normally I enable standard network access to APIs, but for previews you should never do that because previews need to be fast.

I found this blog post from Guardsquare and it helped understand more about what is happening under the hood when Xcode generates a preview. Once I realized that my SceneDelegate code was running, it was clear that I needed to stop these services from starting and a UI generated. I just want the previews to be generated.

Luckily the same blog post above lists the solution. There is an environment variable set by Xcode that you can look for to see if the app is running to generate previews or not. The environment variable is XCODE_RUNNING_FOR_PREVIEWS=1. So I added another function to check if this is set:

private func arePreviewsRunning() -> Bool {
  return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != nil
}

Now you can adjust your SceneDelegate to check both unit tests and previews to prevent any unnecessary work:

if !areTestsRunning() && !arePreviewsRunning() {
  ... do the normal UI stuff here
}

So if you are getting some crashes preventing your previews from working, be sure to check your SceneDelegate and ensure it isn’t starting things or presenting UI that you don’t want. And always look at how you are setting up your views for previews and prevent any unnecessary work so the previews remain fast and crash free.