2

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?

Community
  • 1
  • 1
Julien
  • 125
  • 2
  • 10
  • 1
    This probably isn't what you wanted to hear, but in my opinion what you were doing was always wrong. You've no business hacking an extra UIWindow into the view hierarchy in this way. — However, to be more helpful: is this really something you want to unit test? It sounds more like a candidate for UI testing. – matt Jul 14 '16 at 00:42
  • I don't think this is hacking. See [this post](http://stackoverflow.com/questions/8232398/advantages-problems-examples-of-adding-another-uiwindow-to-an-ios-app) for where adding UIWindows makes sense. I think this is one of those - we want this alert to "float" above other UI Elements. Also, the post linked in my question suggests that this is Apple's internal solution to the UIAlertController question – Julien Jul 14 '16 at 18:30
  • Apple's _own_ alerts (like, a phone call comes in) are certainly windows. But that's outside the app. It doesn't mean _you_ should do that, and it's not how UIAlertController works. Look, I can give you a totally normal non-hacky non-window way to make a custom alert "float" above other UI Elements if you like. But of course it's totally up to you what road you want to go down. – matt Jul 14 '16 at 18:33
  • Well yeah I was following the post linked at the start of my question. If there's a better way to do that I'm all ears – Julien Jul 15 '16 at 18:05
  • The correct modern way is demonstrated in https://github.com/mattneub/custom-alert-view-iOS7 - the position, size, and content of the floating alert are completely up to you. It happens that I've tried to make it look like a UIAlertController's view, but you are not required to do that by any means. – matt Jul 15 '16 at 18:47
  • I am having a [similar issue](https://stackoverflow.com/questions/48099154/uiwindow-makekeyandvisible-throws-props-must-have-a-valid-clientid-error-in?rq=1) when trying to test a framework instead of a normal iOS application. – MattL Apr 09 '18 at 15:04

2 Answers2

5

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.

quellish
  • 21,123
  • 4
  • 76
  • 83
  • I have a framework that tests whether these UIKIt-related side effects have occured and I'm currently trying to embed the framework inside a dummy application. Have you tried doing that before and can offer any guidance? – bartlomiej.n Jun 17 '18 at 08:08
0

We have not resolved the question of whether a UIWindow can be set up to work in a Unit Testing environment, but as @matt's comment got me thinking, this probably isn't something we need to be unit testing in the first place, for the following reasons:

  1. We already have UI Tests that run through alert scenarios. We'll notice very quickly if a build breaks alerts being displayed
  2. For the rest of unit testing purposes, we can simply stub this method to immediately call the completion callback.
  3. Per the original motivation for the question, there doesn't seem to be a way to properly use UIWindow objects in unit tests.
Julien
  • 125
  • 2
  • 10