Functional testing in Swift. Simulate application flow

I am trying to execute some very simple functions / function tests in Swift, but I have some doubts that I need to solve in order to create useful tests.

I want to verify that the Controller represented by another Controller exists in the application navigation hierarchy (it doesn't matter if the Controller was represented in the NavigationController as Modal or something else).

If I create an instance and show the controllers programmatically, directly in the test functions, when I test the On Top controller, I always get the Storyboard root controller instead of the controller, which I just created the instance, as if the manually created controllers are never added to the hierarchy applications.

Here is 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") } 
+8
swift testing functional-testing
source share
3 answers

If I create an instance and show the controllers programmatically, directly in the test functions, when I test the On Top controller, I always get the Storyboard root controller instead of the controller that I just created, as if the controllers that I have manually created are never added into the application hierarchy.

From the code you wrote, you do not check the On Top controller, but you check the root view controller itself (which contains all the view controllers in the hierarchy, including the navigation controllers), so you always return the root view controller. To get the largest part of the controller from the view controller, you can use the following recursive function, which takes the root view controller and returns its topmost 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 test 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") } 
+5
source share

You first stated that it doesn't matter if the view is displayed by the navigation controller. Therefore, I created an empty application with a navigation controller as the initial controller and two ViewControllers, the first is only the namend ViewController , the second is in my case ViewControllerSecond , which is your TheExpectedClass controller.

The first thing to note: when using the NavigationController, it is obvious that the rootController will always be the navigationController. So, let's check what happens if we first load the ViewController , and then inside that click 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 the ViewControllerSecond been ViewControllerSecond on the navigationController, as it should be.

0
source share

If I create and show controllers programmatically, directly to control functions, when I check the On Top controller, I always get the Storyboard Root controller instead of the controller, which I just have created an instance, as if manually created controllers are never added to the application hierarchy .

What you say here is true, they are not added. In your pseudo code, all you did was create some view controllers and push them against each other.

Why do you expect them to be in the application hierarchy? You never added them there.

There are two problems here, and this is only the first.

The second problem:

UIApplication.sharedApplication().keyWindow?.rootViewController

This code captures the root view controller, which is actually the one that is at the very bottom (assuming that the "top" means more visible). When you use a storyboard, it will almost always be the initial controller.

That way, even if you add new view hierarchies to the hierarchy, the test you do will still fail.

Proposed solution

As a simple test, you don’t need to verify that your new view controller is on top of the visual hierarchy. For this you need to add it there.

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

Thus, your test does not depend on the state of the application or other controllers in the hierarchy.

Pseudocode:

 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 embedded in one XCTAssert(nav.visibleViewController.self == TheExpectedClass, "Controller is not what we expect") } 
0
source share

All Articles