Unit Testing View Controllers

James Pang
Tech with Pangers
Published in
3 min readMar 3, 2015

--

I have been wanting to incorporate unit testing into my programming for a while now. Ever since I’ve jumped onto the Swift train, I thought now is as good a time as ever, with a clean, new slate and all. I set about going to add some tests to my view controller, however I hit a couple of road bumps in the process which I hope share with others. I have created a sample project on GitHub which you may follow along with.

The sample contains a UINavigationController as the initial view controller. The root view controller of the UINavigationController is FirstViewController. There is a button on FirstViewController that segues to SecondViewController. In SecondViewController there is an empty textfield.

The two tests I would like to add are:

  1. Check button title in FirstViewController is “Next Screen”
  2. Check textfield in SecondViewController is empty, “”

Firstly, when setting up unit tests, I have heard reports of adding your swift files to both the main target and the test target is not good practice. In light of that, it is better to mark any classes, methods, variables that you would like to access in your unit tests public. Furthermore, you must set the “Defines Module” of the main application target to YES and show below.

Now whenever you decide to make a new test class, you simply put

import MainAppTarget

at the top of the test file.

After creating a new ‘Test Case Class’ the question I asked myself was, how do I instantiate the view controller I want to test? I had tried:

var viewController: FirstViewController!override func setUp() {
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
let navigationController = storyboard.instantiateInitialViewController() as UINavigationController
viewController = navigationController.topViewController as FirstViewController
viewController.viewDidLoad()
}
func testCheckButtonHasTextNextScreen() {
XCTAssertEqual(viewController.button.currentTitle!, "Next Screen", "Button should say Next Screen")
}
however, this caused the test to fail.After searching for an answer, the solution was to instantiate the view controller with a storyboard (remember to give your view controller a storyboard ID) like the followingviewController = storyboard.instantiateViewControllerWithIdentifier("FirstViewController") as FirstViewControllerHowever, instantiating the view controller like this ONLY instantiates the view controller alone, and not any navigation controllers that it may be embedded in. That means, attemping to doXCTAssertFalse(viewController.navigationController!.navigationBarHidden, "Bar should show by default")will result in a nil exception. However, since I wanted to check the state of the navigation bar in FirstViewController, you must instantiate the view controller like so:let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController = storyboard.instantiateInitialViewController() as UINavigationController
viewController = navigationController.topViewController as FirstViewController
Now performing the testXCTAssertFalse(viewController.navigationController!.navigationBarHidden, "nav bar should be showing by default")results in a successful test.Another lesson that I learnt was that in order to trigger the viewDidLoad() method of a view controller, instead of manually calling viewController.viewDidLoad(), you can access the view property of the view controllerlet _ = viewController.viewFurthermore, doing the above will not trigger viewWillAppear() and anything afterwards as well. As a result, these methods need to be called manually if you would like to trigger any code within these methods.Hopefully, you learned a little bit of how view controllers behave when it comes to unit testing. If you have any questions, please feel free to let me know.

--

--