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.

Improve your test writing “Flow.”
Sign up to get my test-oriented code snippets.

More...
Testing code that calls Apple's frameworks
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:
- You call the framework — but not really. Instead, substitute a test double in its place. Use the double to verify that the framework was called correctly.
- The framework calls you — but not really. Instead, have your test code call your production code directly, as if it were the framework.
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.)
I don't care so much about "units." I just want fast, reliable feedback to support my TDD habit.
Using dependency injection
“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…
Method swizzling, RAII, and Ambient Context injection
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.
UIAlertController test examples
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.
func test_alertTitle() {
let alertVerifier = QCOMockAlertVerifier()
sut.showAlert()
XCTAssertEqual(alertVerifier.title, "Title")
}
And here’s a test that executes the block associated with a particular button:
func test_alertOKButton() {
let alertVerifier = QCOMockAlertVerifier()
sut.showAlert()
alertVerifier.executeActionForButton(withTitle: "OK")
// Now assert what you want
}
(My original API around retrieving a block this was clumsy. Thanks to PivotalCoreKit for the inspiration.)
The libraries are available for use & study
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.
I always avoid swizzling, giving in only when I cannot find a better, or at least equal alternative.
Really great article. Thanks for sharing!
I’m with you, Ron: given a choice, I’d rather avoid swizzling. I’m sure I’m not the first person to use a controlling object to do so, but as I wrote the post, it was an a-ha moment to see a connection with my C++ past.
My approach on this is another one that does not need any swizzling. I do not use the UIAlertController directly in the code. I have OBDialogBuilder class that is factory class that creates the dialog using the UIAlertController or UIActionSheet. I have two concrete implementations because I also need to support iOS 6 and 7 and I configure the injector to use the proper implementation depending on the OS version.
Here is what the OBDialogBuilder interface looks like:
typedef void (^OBDialogButtonCompletionBlock)(void);
@interface OBDialogBuilder : NSObject
- (OBDialogBuilder *)setTitle:(NSString *)string;
- (OBDialogBuilder *)setMessage:(NSString *)string;
- (OBDialogBuilder *)addButtonWithTitle:(NSString *)title completion:(OBDialogButtonCompletionBlock)completion;
- (OBDialogBuilder *)addDestructiveButtonWithTitle:(NSString *)title completion:(OBDialogButtonCompletionBlock)completion;
- (OBDialogBuilder *)addCancelButtonWithTitle:(NSString *)title completion:(OBDialogButtonCompletionBlock)completion;
- (OBDialogBuilder *)addOkButton;
- (OBDialogBuilder *)addCancelButton;
- (void)showDialogWithViewController:(UIViewController *)controller;
@end
(The concrete implementation using the UIAlertController should be a no brainer)
I also use an OBDialogBuilderStub implementation to avoid mocking and make tests simpler to write and read. Here is what a concrete tests looks like:
OBDialogBuilderStub *dialogBuilderStub = [[OBDialogBuilderStub alloc] init];
myViewController.dialogBuilder = dialogBuilderStub;
[myViewController deletePressed:nil];
assertThat(dialogBuilderStub.title, is(NSLocalizedString(@“MY_ALERT_TITLE”, @“”)));
assertThat(dialogBuilderStub.message, is(NSLocalizedString(@“MY_ALERT_TEXT”, @“”)));
assertThat(dialogBuilderStub.buttons, hasCountOf(2));
assertThatBool(dialogBuilder.hasDestructiveButton, is(@YES));
assertThatBool(dialogBuilder.hasOkButton, is(@YES));
Here is the implementation in the myViewController:
- (IBAction)deletePressed:(id)sender {
[self.dialogBuilder setTitle:NSLocalizedString(@“MY_ALERT_TITLE”, @“”)];
[self.dialogBuilder setMessage:NSLocalizedString(@“MY_ALERT_TEXT”, @“”)];
__weak MyViewController *weakSelf = self;
[dialogBuilder addDestructiveButtonWithTitle:NSLocalizedString(@“MY_DELETE_ACTION_BUTTON”, @“”) completion:^{
[weakSelf delete];
}];
[dialogBuilder addCancelButton];
[dialogBuilder showDialogWithViewController:self];
}
I have also a
- (void)pressButtonWithTitle:(NSString *)title;
method in the OBDialogBuilderStub so that a button press is triggered and the proper block is executed.Nice: the Facade pattern. Thanks for sharing!
Hey, thanks for the article but only one question, is it really need to tests UIAlertControllers ? I mean maybe it is enough to test a method calls UIAlertControllers code, isn’t it?
That’s basically what I’m doing. My custom mock tests how calls are made to UIAlertController, without actually executing them.
What is a clean way to test an alert controller with the style of UIAlertControllerStyleActionSheet on a universal app, that when on an iPad needs a style of UIAlertControllerStyleAlert. Currently I had to put logic in my tests, which seems wrong. My test look something like this.
- (void)test_alertController_Default_AlertControllerStyleShouldBeActionSheet {
[self.sut alertController];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[MKTVerify(self.sut.UIAlertControllerClass) alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleAlert];
}
else {
[MKTVerify(self.sut.UIAlertControllerClass) alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
}
}
I should note that I am converting to this QCOMockAlertVerifier to clean up some of the property injection of classes.
Thanks,
Deric
Yeah, that’s really two different tests. What you want is a way to control the UI idiom in tests. For example, you could pass it as a parameter. Then have one test for pad, another for phone.