Unit Testing UITabBarControllers

Previously, I wrote about how to unit test UIViewController. However, in the real world, a UIViewController is usually embedded in a UINavigationController and a UITabBarController.

There are a couple ways to setup unit tests in this case. First of all, you can reach through the controller stack to setup the UIViewController that is the system under test.

var sut: FirstViewController!
    
override func setUp() {
    super.setUp()
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let tab = storyboard.instantiateInitialViewController() as! UITabBarController
    UIApplication.shared.keyWindow?.rootViewController = tab
    let nav = tab.selectedViewController as! UINavigationController
    sut = nav.topViewController as! FirstViewController
    UIApplication.shared.keyWindow?.rootViewController = sut
}

Note that you have to assign the UITabBarController to the rootViewController to run the view lifecycle and then assign the UIViewController to the rootViewController to run its view lifecycle.

Once again, you should not directly call loadView.

A UITabBarController owns an array of viewControllers. The selectedIndex variable can changed to select another UIViewController to test. Once again, make sure to assign it to rootViewController to run the view lifecycle methods.

var sut: SecondViewController!

override func setUp() {
    super.setUp()
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let tab = storyboard.instantiateInitialViewController() as! UITabBarController
    UIApplication.shared.keyWindow?.rootViewController = tab
    tab.selectedIndex = 1
    let nav = tab.selectedViewController as! UINavigationController
    sut = nav.topViewController as! SecondViewController
    UIApplication.shared.keyWindow?.rootViewController = sut
}

The above code can be simplified by directly instantiating the UIViewController.

var sut: SecondViewController!

override func setUp() {
    super.setUp()
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    sut = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
    UIApplication.shared.keyWindow?.rootViewController = sut
}

The above code is simpler but it does not ensure that the UIViewController is in the viewController array.