Test the UIAlertController method in Swift

  • 2020-05-09 19:22:36
  • OfStack

I recently read an article using control swizzling to test UIAlertController in Objective-C. Articles like this always prompted me to look for a way to test the same thing without using control swizzling. Although I know swizzling is a very powerful tool for developers, I personally try to avoid using it as much as possible. In fact, I've only used swizzling for one app in the last six years. So I believe we can now implement testing without swizzling.

So the question is, how do you test UIAlertController without using swizzling in Swift?

Let's start with the code we want to test. I have added a button to Storyboard. (I use Storyboard to make it more intuitive for those who don't want to code the interface.) when you press this button, a popover (alert) appears, which has a title, message content, and two buttons, OK and cancel (Cancel).

Here's the code:


import UIKit
class ViewController: UIViewController {
 var actionString: String?
 @IBAction func showAlert(sender: UIButton) {
  let alertViewController = UIAlertController(title: "Test Title", message: "Message", preferredStyle: .Alert)
  let okAction = UIAlertAction(title: "OK", style: .Default) { (action) -> Void in
   self.actionString = "OK"
  }
  let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) -> Void in
   self.actionString = "Cancel"
  }
  alertViewController.addAction(cancelAction)
  alertViewController.addAction(okAction)
  presentViewController(alertViewController, animated: true, completion: nil)
 }
}

Note that the popover actions in this example do nothing concrete, they only indicate that they can validate unit tests.

Let's start with a simple test: test the title and message content of the popover controller.

The test code is as follows:


import XCTest
@testable import TestingAlertExperiment
class TestingAlertExperimentTests: XCTestCase {
 var sut: ViewController!
 override func setUp() {
  super.setUp()
  sut = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as! ViewController
  UIApplication.sharedApplication().keyWindow?.rootViewController = sut
 }
 override func tearDown() {
  // Put teardown code here. This method is called after the invocation of each test method in the class.
  super.tearDown()
 }
}
```

We need to set sut to the root view controller, otherwise the view controller can't pop up this popover view controller.

The code to add the UIAlertController test title is as follows:


```Swift
func testAlert_HasTitle() {
 sut.showAlert(UIButton())
 XCTAssertTrue(sut.presentedViewController is UIAlertController)
 XCTAssertEqual(sut.presentedViewController?.title, "Test Title")
}
``` 

It's easy. Now let's test the cancel button on UIAlertController. There is one problem: you cannot get the closure of the popover action. So we need to simulate the popover action in order to store the handler and call it in the test to see if the popover action is what we expected. Add the following class to the test case:


```Swift
class MockAlertAction : UIAlertAction {
 typealias Handler = ((UIAlertAction) -> Void)
 var handler: Handler?
 var mockTitle: String?
 var mockStyle: UIAlertActionStyle
 convenience init(title: String?, style: UIAlertActionStyle, handler: ((UIAlertAction) -> Void)?) {
  self.init()
  mockTitle = title
  mockStyle = style
  self.handler = handler
 }
 override init() {
  mockStyle = .Default
  super.init()
 }
}

The main job of this mock class is to capture the handler block for later use. Now we need to insert the simulated class into the implementation code. Replace the code in the view controller with this:


import UIKit
class ViewController: UIViewController {
 var Action = UIAlertAction.self
 var actionString: String?
 @IBAction func showAlert(sender: UIButton) {
  let alertViewController = UIAlertController(title: "Test Title", message: "Message", preferredStyle: .Alert)
  let okAction = Action.init(title: "OK", style: .Default) { (action) -> Void in
   self.actionString = "OK"
  }
  let cancelAction = Action.init(title: "Cancel", style: .Cancel) { (action) -> Void in
   self.actionString = "Cancel"
  }
  alertViewController.addAction(cancelAction)
  alertViewController.addAction(okAction)
  presentViewController(alertViewController, animated: true, completion: nil)
 }
}

```

We added a class variable 'Action' and set it to 'UIAlertAction.self'. This is the variable we will use when initializing the popover action. This allows us to override it when we test it. Like this:


```Swift
func testAlert_FirstActionStoresCancel() {
 sut.Action = MockAlertAction.self
 sut.showAlert(UIButton())
 let alertController = sut.presentedViewController as! UIAlertController
 let action = alertController.actions.first as! MockAlertAction
 action.handler!(action)
 XCTAssertEqual(sut.actionString, "Cancel")
}

First we insert this popover action. And then we call our code to pop up the popover view controller. We got the cancel action from the rendered view controller and successfully called the captured handler block. The last step is to determine whether the current action is the one we expected.

That's it, a very simple way to test UIAlertViewController without using swizzling.

So that's how you can test UIAlertController in Swift.


Related articles: