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:

Leave a Comment:

8 comments
Add Your Reply