TL;DR — Using an empty app delegate for unit testing is great for iOS developers. With a little modification, Mac developers can do the same.
App Delegate — Not in Charge Anymore
At Oak City Labs, we’re big believers in the power of unit testing which is vital to the health and reliability of our build automation process. Jon Reid runs a great blog called Quality Coding, focusing on Test Driven Development (aka TDD) and unit testing. Jon’s blog is one of our secret weapons for keeping up with new ideas and techniques in testing.
A few months ago, I read Jon’s article, “How to Easily Switch Your App Delegate for Testing”. It’s a quick read detailing a neat trick for speeding up execution of your unit tests. The gist is that you switch UIAppDelegate classes at startup, before the bulk of your app has bootstrapped. By switching to a UIAppDelegate just for testing, which does absolutely nothing, you bypass the app’s startup routine that slows down test execution. Faster tests mean less time waiting and less pain associated with testing. 🎉
There’s also another benefit that Jon doesn’t really mention. Because you skip the normal startup routine, the only code executed must be called by your test. Say I’m writing a test for my DataController without using this technique. The test is failing and I drop a breakpoint in the initialization routine. When I run the test, the the debugger stops at the breakpoint twice — once because the app is bootstrapping itself and once for the unit test that creates its own DataController. Now there are two DataController instances running around. Comedy hijinks ensue!
On the other hand, if you switch to an empty UIAppDelegate for testing, we can eliminate the bootstrap altogether, meaning only one instance of DataController is created and that’s part of our unit test. No more confusion about whether an object in the system is under test. By dynamically choosing a testing UIAppDelegate, our tests run faster, there is less confusion and, as Jon points out, it becomes easy to test our production UIAppDelegate too.
Back to the Mac
Hopefully you’re convinced at this point that choosing an UIAppDelegate at runtime is a Very Good Idea. Setting all this up for an iOS project is thoroughly discussed in the original article, including variants for Objective-C and both Swift 2 and 3. At Oak City Labs, we write Mac Apps too, so how does this translate from mobile to desktop?
For reference, here’s an implementation of main.swift I’m using in an iOS Swift 2 project.
This is pretty straightforward, since `UIApplicationMain` takes the name of the UIAppDelegate class as one of it’s parameters. Unfortunately, when we move to the Mac, that’s not how `NSApplicationMain` works. Showing it’s C roots, `NSApplicationMain` just takes `argc` and `argv`. So, in order to make this work on the desktop, we need to do a little extra jiggery pokery.
Running normally, we just call NSApplicationMain like always. Running in unit test mode, we need to manually create our empty NSAppDelegate and explicitly set it as the delegate property of the global shared app instance. Then we’re ready to kick off the run loop with `[NSApp run]`.
Side note — I started writing this in Swift 3 since the rest of the project is Swift 3, but I’m still new to Swift 3 and I couldn’t manage to get the instantiation from a class name bit working. Luckily, I realized I could still write my `main()` routine in trusty, old Objective-C and it would play nicely with my type-safe Swift application.
Just for reference, here’s my TestingAppDelegate class. Like school on Saturday, this class is empty.
We want unit tests to provide fast feedback, and clear feedback. Using the empty TestingAppDelegate approach makes our testing better at both. Now, with a little runtime hocus-pocus, we can employ delegate switching on the Mac as well as iOS.