MatterGoal
MatterGoal

Reputation: 16430

Functional testing in Swift. Simulate the Application flow

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")
}

Upvotes: 8

Views: 549

Answers (3)

Jack
Jack

Reputation: 16855

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")
}

Upvotes: 0

Zell B.
Zell B.

Reputation: 10286

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")
}

Upvotes: 5

Alex
Alex

Reputation: 541

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.

Upvotes: 0

Related Questions