iOS: The One Weird Trick For Testing View Controllers in Swift

I’ve been testing View Controllers in Swift for the past year, but for some reason, last week all my tests would just randomly start failing due to some type of timer / race condition issues. Turns out, I was setting up my View Controller tests incorrectly:

The problem was clearly with the last line in my setup NSRunLoop.mainRunLoop().runUntilDate(NSDate()) . Since the goal here is to make sure that the UIViewController life cycle functions – including viewDidLoad and viewDidAppear – get called correctly, I replaced this line with viewController.loadView(). I ran into the same frustrating issue.

So after googling around, I found an amazing presentation from the testing expert @modocache: Everything You (N)ever Wanted to Know about Testing View Controllers . Go ahead and flip though it. I’ll wait!

Instead of using NSRunLoop.mainRunLoop().runUntilDate(NSDate()) or viewController.loadView(), the key is to use let _ = viewController.view like this:

I’ll be honest and be the first to admit that this is not a solution I would have naturally come up with ever. After discussing it with @allonsykraken, I kind of understand why it works.

The key here is that Apple overrides the viewController’s view getter to call the loadView function and do a bunch of other things we have no access to. If anyone else has other great insights into why this works, feel free to add it in the comments!

I went ahead and updated my other blog posts on View Controller testing if you’re interested in learning more:

UPDATE: As Tomasz Szulc points out below in the comments, Apple has a whole explanation in the documentation about why you shouldn’t call loadView():

You should never call this method directly. The view controller calls this method when its view property is requested but is currently nil. This method loads or creates a view and assigns it to the view property.

If the view controller has an associated nib file, this method loads the view from the nib file. A view controller has an associated nib file if the nibName property returns a non-nil value, which occurs if the view controller was instantiated from a storyboard, if you explicitly assigned it a nib file using the initWithNibName:bundle: method, or if iOS finds a nib file in the app bundle with a name based on the view controller’€™s class name. If the view controller does not have an associated nib file, this method creates a plain UIView object instead.

If you use Interface Builder to create your views and initialize the view controller, you must not override this method.

You can override this method in order to create your views manually. If you choose to do so, assign the root view of your view hierarchy to the view property. The views you create should be unique instances and should not be shared with any other view controller object. Your custom implementation of this method should not call super.

If you want to perform any additional initialization of your views, do so in the viewDidLoad method.

UPDATE 2: I really like @salutis‘s suggestion on making this a lot more readable:

Here is what the code would look like:

UPDATE 3: (This is the reason I love blogging. Learning so much right now!) Another great Swift tip from @jckarter that I didn’t know about!

So your extension can now be even prettier like this:

UPDATE 4: @joemasilotti has a less hacky solution to loading the view!

Enjoy the article? Join over 20,000+ Swift developers and enthusiasts who get my weekly updates.