8

I'm trying to perform some really simple feature/functional testing in Swift but I have some doubts that I need to resolve to be able to create useful tests.

I want to verify that a Controller presented by another Controller exists into the Application Navigation Hierarchy (it doesn't matter if the Controller has been presented into a NavigationController, as Modal or whatever).

If I instantiate and show controllers programmatically, directly into the test functions, when I check the On Top controller I always get the Storyboard root controller instead of the controller that I have just instantiated, as if the controllers that I've manually created are never added into the Application Hierarchy.

Here an example of pseudo-code:

func testController(){ 

    // Instantiate a controller 
    let storyBoard:UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
    let controller1 = storyBoard.instantiateViewControllerWithIdentifier("Controller1") as? ControllerOneViewController
    controller1.loadView()

    // Call a function that instantiates another controller 
    controller1.pushAnotherController()

    // Test that the current shown controller is what we expect... 
    let rootController = UIApplication.sharedApplication().keyWindow?.rootViewController
    XCTAssert(rootController.self == TheExpectedClass, "Controller is not what we expect")
}
MatterGoal
  • 16,038
  • 19
  • 109
  • 186
  • 1
    you should show what controller1.pushAnotherController() does. Your post does not show of which type the suspected class should be. – Alex Feb 16 '15 at 22:27
  • this infornation is not relevant. They could be any kind of controller and presented in different ways – MatterGoal Feb 19 '15 at 19:18
  • I say it is relevant to check where the problem could be. If you say it is not relevant you should post a more general solution like pushing another controller directly in this block. IMHO – Alex Feb 20 '15 at 09:26

3 Answers3

5

If I instantiate and show controllers programmatically, directly into the test functions, when I check the On Top controller I always get the Storyboard root controller instead of the controller that I have just instantiated, as if the controllers that I've manually created are never added into the Application Hierarchy.

From the code you wrote you are not checking the On Top controller but you are checking root view controller itself (which holds all view controllers in hierarchy including navigation controllers) so thats why you get always storyboard root view controller back. To get top most controller from view controller you can use the following recursive function which takes root view controller and return its top most controller

func topMostController(rootViewController:UIViewController)->UIViewController{

    if let viewController = rootViewController as? UINavigationController{

        return topMostController(viewController.visibleViewController)
    }

    if let viewController = rootViewController.presentedViewController{

        return topMostController(viewController)
    }

    return rootViewController
}

and then in your testing function check the controller that this function returns

func testController(){ 

// Instantiate a controller 
let storyBoard:UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
let controller1 = storyBoard.instantiateViewControllerWithIdentifier("Controller1") as? ControllerOneViewController
controller1.loadView()

// Call a function that instantiates another controller 
controller1.pushAnotherController()

// Test that the current shown controller is what we expect... 
let rootController = UIApplication.sharedApplication().keyWindow?.rootViewController
XCTAssert(topMostController(rootController) == TheExpectedClass, "Controller is not what we expect")
}
Zell B.
  • 10,266
  • 3
  • 40
  • 49
0

First you stated that it does not matter if the view is presented by a navigation controller. So i created a empty application with a navigationcontroller as initial controller and two ViewControllers, the first is just namend ViewController the second is in my case ViewControllerSecond which is your TheExpectedClass Controller.

First thing to note: If using a NavigationController obviously the rootController will always be the navigationController. So then lets check what happens if we first load the the ViewController and then within this push the ViewControllerSecond:

    let storyBoard:UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
    let controllerSecond = storyBoard.instantiateViewControllerWithIdentifier("ViewControllerSecond") as? ViewControllerSecond

    controllerSecond?.loadView()
    self.navigationController?.pushViewController(controllerSecond!, animated: false)

    let navigationController = UIApplication.sharedApplication().keyWindow?.rootViewController as UINavigationController
    let currentController: AnyObject = navigationController.viewControllers[0]
    println(navigationController.viewControllers)

You will see that ViewControllerSecond was pushed to the navigationController as it should.

Alex
  • 541
  • 5
  • 19
0

If I instantiate and show controllers programmatically, directly into the test functions, when I check the On Top controller I always get the Storyboard root controller instead of the controller that I have just instantiated, as if the controllers that I've manually created are never added into the Application Hierarchy.

What you're saying here is true, they aren't added. In your pseudo code all you did was instantiate some view controllers and push them onto each other.

Why are you expecting them to be in the Application Hierarchy? You never added them there.

There are actually two issues here, and that is only the first one.

The second issue:

UIApplication.sharedApplication().keyWindow?.rootViewController

This code grabs the root view controller, which is actually the one that is on the very "bottom" (assuming "top" means more visible). When you are using a Storyboard this will almost always be the initial view controller.

So even if you did add your newly instantiated view controllers to the hierarchy, the test you do will still not pass.

Suggested solution

As a simple test, you don't need to test that your new View Controller is on top the visual hierarchy. To do so you would need to add it there.

All you really need to test is - "If I push my view controller onto this newly created navigation stack, it should be at the top of that stack (visible)"

This way your test is not dependent on the application state or any other controllers that are in the hierarchy.

Pseudo code:

func testController(){ 
  // Instantiate a controller 
  let storyBoard:UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
  let controller1 = storyBoard.instantiateViewControllerWithIdentifier("Controller1") as? ControllerOneViewController
  controller1.loadView()

  // Call a function that instantiates another controller 
  controller1.pushAnotherController()

  // Test that the current shown controller is what we expect... 
  let nav = controller1.navigationController! //Assuming it's embedded in one
  XCTAssert(nav.visibleViewController.self == TheExpectedClass, "Controller is not what we expect")
}
Jack
  • 16,677
  • 8
  • 47
  • 51
  • thank you @Jack really interesting. How would you test the presentation of a Modal Controller using this logic? – MatterGoal Feb 25 '15 at 15:04
  • You could make use of the `topMostController` function in zelb's answer. It will recursively go through push's and present's and find the top most controller. However, pass in `controller1` instead of `.rootViewController` for the same reasons in my answer - your `controller1` is actually not pushed onto `rootViewController` anywhere and you don't need to do that to test it. In fact, if you did push it onto the hierarchy your test would be dependant on code that runs outside of your test (ie you change the window somewhere else the test would fail), which is undesirable. – Jack Feb 25 '15 at 15:07