Reputation: 16430
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
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
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
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