Quality Coding
Shares

How to Test UIAlertControllers (and Control Swizzling)

Shares

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.

UIAlertController presenting an iPad popover

So… can we write unit tests against a UIAlertController? Let’s learn some tricks for dealing with Cocoa Touch.

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:

  1. 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.
  2. 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.)

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.

- (void)testShowAlert_AlertShouldHaveTitle
{
    QCOMockAlertVerifier *alertVerifier = [[QCOMockAlertVerifier alloc] init];

    [sut showAlert:nil];

    XCTAssertEqualObjects(alertVerifier.title, @"Title");
}

And here’s a test that executes the block associated with a particular button:

- (void)testShowAlert_ExecutingActionForOKButton_ShouldDoSomething
{
    QCOMockAlertVerifier *alertVerifier = [[QCOMockAlertVerifier alloc] init];

    [sut showAlert:nil];
    [alertVerifier executeActionForButtonWithTitle:@"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? Click here to leave a comment.

About the Author Jon Reid

Jon is a coach and consultant on iOS Clean Code (Test Driven Development, unit testing, refactoring, design). He’s been practicing TDD since 2001. You can learn more about his background, or see what services he can bring to your organization.

follow me on:
Disclosure: The book links below are affiliate links. If you buy anything, I earn a commission, at no extra cost to you.

Leave a Comment:

8 comments
Ron Lisle says a couple of years ago

I always avoid swizzling, giving in only when I cannot find a better, or at least equal alternative.
Really great article. Thanks for sharing!

Reply
    Jon Reid says a couple of years ago

    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.

    Reply
René Pirringer says a couple of years ago

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.

Reply
    Jon Reid says a couple of years ago

    Nice: the Facade pattern. Thanks for sharing!

    Reply
Tony Stark says a couple of years ago

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?

Reply
    Jon Reid says a couple of years ago

    That’s basically what I’m doing. My custom mock tests how calls are made to UIAlertController, without actually executing them.

    Reply
Deric says a couple of years ago

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

Reply
    Jon Reid says a couple of years ago

    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.

    Reply
Add Your Reply