With the imminent release of iOS 9 and the usual fast adoption rate, many developers can finally start using APIs introduced in iOS 8. One of these is UIAlertController, which unifies code for alerts, action sheets, and iPad popovers.
So… can we write unit tests against a UIAlertController? Let’s learn some tricks for dealing with Cocoa Touch.
As I’ve shown in my screencast of Test-Driven Development of a UIViewController, there’s a pattern for testing code that calls Apple’s frameworks. Remember, the way frameworks work is you call the framework, then the framework calls you. So for testing:
This is the pattern I use when there’s a boundary I don’t want to cross during unit testing. (I don’t care so much about “units”. I just want fast, reliable feedback to support my TDD habit.)
“Substitute a test double in its place” really means taking that dependency and making it so we can inject a double. As I explain in my objc.io article, there are various forms of dependency injection.
I lean towards constructor injection because it makes the dependencies explicit.
To substitute an Apple-provided class, I prefer property injection where a lazy getter provides the default class. At the expense of some ugliness, it makes the injectable points very clear.
I try to avoid method swizzling, because you can no longer look at the calling code and clearly see injectable dependencies.
But for my new MockUIAlertController testing library, I had to do some underlying method swizzling to make things testable. (Apple, when you take a block in UIAlertAction, it would make testing simpler if you exposed it as a read-only property!)
So since I was swizzling anyway…
By request, I took the swizzling all the way so that no changes are necessary in the calling code. Just create the UIAlertController the normal way, by invoking
+alertControllerWithTitle:message:preferredStyle. Create UIAlertActions and add them to the controller. Have your view controller present the UIAlertController. No weird tricks here.
So when does this same code interact with test doubles, instead of actually presenting an alert (which would block unit tests)? When a QCOMockAlertVerifier exists.
The QCOMockAlertVerifier performs the required method swizzling when created. And it swizzles everything back when destroyed. Similar to C++ RAII, the very existence of the verifier performs Ambient Context dependency injection.
So the production code doesn’t have to change. But how clear is the test code?
Here’s a test that verifies the title of an alert.
sut is the “system under test” in the test fixture.
And here’s a test that executes the block associated with a particular button:
MockUIAlertController is available for UIAlertController tests. Using the same patterns, I also created MockUIAlertViewActionSheet for old-style code that uses UIAlertView and UIActionSheet. This replaces the library I originally shared in my older post How to Unit Test Your Alerts and Action Sheets.
I hope you find these libraries useful. Beyond their testing functionality, the code illustrates how to control method swizzling in a RAII style through init/dealloc. More specifically, we can use the lifetime of a test helper to manage Ambient Context dependency injection.
When have you used method swizzling for test purposes, and when have you avoided it? Leave a comment below.
Please log in again. The login page will open in a new window. After logging in you can close it and return to this page.