Kasper Munck

Testing a View Controller's IBOutlet Connections

When Using Nib Files

Understanding how a ViewController's view hierarchy is loaded is essential when testing UI related stuff. Consider the following test case, which verifies that an IBOutlet connection to myLabel has been established when a view controller is loaded:

MyViewController *vc = [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];
STAssertNotNil(vc.myLabel, @"Should connect myLabel IBOutlet.");

The test will always fail, telling you that vc.myLabel is nil. There is a very good reason for that. Apple's View Controller Programmer Guide for iOS illustrates a view controller's loading and unloading sequence with the following diagram:

It is described as follows:

Whenever some part of your app asks the view controller for its view object and that object is not currently in memory, the view controller loads the view hierarchy into memory and stores it in its view property for future reference.

This explains why myLabel is always nil in the test case above; it has not yet been loaded, since - at the time STAssertNotNil is reached - the view property has not yet been accessed. To force loading of the view hierarchy, invoke the view property of the viewcontroller:

MyViewController *vc = [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];
[vc view]; // load view hierarchy (if not already loaded)
STAssertNotNil(vc.myLabel, @"Should connect myLabel IBOutlet.");

Now, as opposed to before, the test passes, because the view controller loads the view hierarchy into memory and stores it in its view property

When using Storyboards

When using storyboards, initializing a viewcontroller under test with initWithNibName:bundle: or init no longer makes sense, since the views are now embedded in a storyboard, hence no longer accessible via a nib file. To be able to load a viewcontroller realistically when using storyboards, follow these two steps:

  • Assign an identifier string to the viewcontroller in the storyboard.
  • Load the storyboard programmatically and ask it for a viewcontroller with a desired identifier.

Assume a storboard containing the view of MyViewController. First, in the Storyboard, select MyViewController and, in Utilities View, select the Identity Inspector. In here you will find an Identity Setting named Storyboard ID. This is used to identify the viewcontroller. Assign a unique, descriptive value, eg MyViewController.

Storyboard viewcontroller identifier

Now, when initializing MyViewController from test code, follow this pattern to load it programmatically (where Storyboard is the name of the containing storyboard file):

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
MyViewController *vc = [storyboard instantiateViewControllerWithIdentifier:@"MyViewController"];

With this approach you don't have to invoke the view property, since the view hierarchy has already been loaded into memory at this time.

[ unit test viewcontroller iboutlet ]