Search code examples
iosswiftunit-testinguialertcontrolleruiwindow

NSInternalConsistencyException when Unit Testing with UIWindow()


I've been using the following post as guidance for how to display a UIAlertController from code that is unrelated to a specific UIViewController. Now, I want to unit test this code:

func showAlert(alert: UIAlertController, animated: Bool, completion: (()->Void)?) 
{
    let alertWindow = UIWindow(frame: UIScreen.mainScreen().bounds)

    // Keep a strong refence to the window. 
    // We manually set this to nil when the alert is dismissed
    self.alertWindow = alertWindow

    alertWindow.rootViewController = UIViewController()        
    if let currentTopWindow = UIApplication.sharedApplication().windows.last {
        alertWindow.windowLevel = currentTopWindow.windowLevel + 1
    }
    else {
        // This case only happens during unit testing
        Logger.trace(ICELogLevel.Error, category: .Utility, message: "The application doesn't have a window being displayed!")
    }

    // preload the viewController for unit testing 
    // (see https://www.natashatherobot.com/ios-testing-view-controllers-swift/ )
    let _ = alertWindow.rootViewController?.view
    alertWindow.makeKeyAndVisible()

    alertWindow.rootViewController!.presentViewController(self.alertController, animated: animated, completion: completion)
}

However, when running a unit test, on the alertWindow.makeKeyAndVisible() line, I get an NSInternalInconsistencyException: props must have a valid clientID.

This code works in the app code, and I'd prefer not to use a mock UIWindow etc, since I'm looking to verify that the alert is actually shown on a real UIWindow.

Any guidance on how we can use UIWindows() in Unit Tests? What am I doing wrong?


Solution

  • The problem is that makeKeyAndVisible invokes code that internally requires a running UIApplication instance. This is why the exception is thrown when testing a framework but not when testing an application. Application tests inject the test bundle into a running application. The easy workaround is to move from:

    alertWindow.makeKeyAndVisible()

    To

    alertWindow.isHidden = false

    This works and allows a view controller to be tested in isolation with a window. The downside is of course that this is not identical to makeKeyAndVisible - it does not have the side effects that trigger the exception.

    Another strategy is to create a new, empty application target exclusively to support testing. Embed the framework under test inside that and use it as the host application for unit tests.