以编程方式在UIAlertController上触发UIAlertAction?

时间:2016-03-23 09:03:13

标签: swift unit-testing uialertcontroller uialertaction

关于这个主题有几个现有的问题,但它们并不是我所追求的。我为我的应用写了一个小的Swift应用评级提示,它提示了两个UIAlertController实例,一个由另一个触发。

我现在正试图对此进行单元测试,并尝试在测试中达到第二个警报。我已经写了一个简单的间谍来检查第一个控制器,但我想要一种方法来触发第一个警报上的一个动作,然后再显示第二个动作。

我已经尝试了alert.actions.first?.accessibilityActivate(),但它似乎并没有打破这一行动的处理程序 - 这就是我所追求的目标。

4 个答案:

答案 0 :(得分:4)

以下是我的所作所为:

  1. 创建了我的类的模拟版本,它将显示警报控制器,在我的单元测试中,使用了这个模拟。

  2. 覆盖我在非模拟版本中创建的以下方法:

    func alertActionWithTitle(title: String?, style: UIAlertActionStyle, handler: Handler) -> UIAlertAction
    
  3. 在重写的实现中,存储了一些属性中的操作的所有详细信息(Handler只是一个类型() -> (UIAlertAction)

    var didCreateAlert = false
    var createdTitles: [String?] = []
    var createdStyles: [UIAlertActionStyle?] = []
    var createdHandlers: [Handler?] = []
    var createdActions: [UIAlertAction?] = []
    
  4. 然后,在运行我的测试时,为了遍历警报的路径,我实现了一个callHandlerAtIndex方法来迭代我的处理程序并执行正确的。

  5. 这意味着我的测试看起来像这样:

    feedback.start()
    feedback.callHandlerAtIndex(1) // First alert, second action
    feedback.callHandlerAtIndex(2) // Second alert, third action
    XCTAssertTrue(mockMailer.didCallMail)
    

答案 1 :(得分:4)

不涉及更改生产代码以允许在单元测试中以编程方式轻敲UIAlertActions的解决方案,我发现in this SO answer

在谷歌搜索答案时,将其张贴在这里以及这个问题对我来说是突然出现的,以下解决方案使我有更多的时间来查找。

将扩展名放在测试目标的下方:

extension UIAlertController {
    typealias AlertHandler = @convention(block) (UIAlertAction) -> Void

    func tapButton(atIndex index: Int) {
        guard let block = actions[index].value(forKey: "handler") else { return }
        let handler = unsafeBitCast(block as AnyObject, to: AlertHandler.self)
        handler(actions[index])
    }
}

答案 2 :(得分:0)

我使用上述卢克的指导来创建UIAlertAction的子类,该子类保存其完成块,以便可以在测试期间调用它:

class BSAlertAction: UIAlertAction {

    var completionHandler: ((UIAlertAction) -> Swift.Void)?

    class func handlerSavingAlertAction(title: String?,
                                        style: UIAlertActionStyle,
                                        completionHandler: @escaping ((UIAlertAction) -> Swift.Void)) -> BoseAlertAction {
        let alertAction = self.init(title: title, style: style, handler: completionHandler)
        alertAction.completionHandler = completionHandler
        return alertAction
    }

}

如果愿意,可以自定义此选项以保存更多信息(例如标题和样式)。这是XCTest的示例,然后使用此实现:

func testThatMyMethodGetsCalled() {
    if let alert = self.viewController?.presentedViewController as? UIAlertController,
        let action = alert.actions[0] as? BSAlertAction,
        let handler = action.completionHandler {
            handler(action)
            let calledMyMethod = self.presenter?.callTrace.contains(.myMethod) ?? false
            XCTAssertTrue(calledMyMethod)
    } else {
        XCTFail("Got wrong kind of alert when verifying that my method got called“)
    }
}

答案 3 :(得分:0)

我根据测试UIContextualAction所采取的策略采用了略有不同的方法,该方法与UIAction非常相似,但是将其handler作为属性公开(不确定为什么苹果不会这样做)。对UIAction做过同样的事情。我将警报动作提供程序(由协议封装)注入了我的视图控制器。在生产代码中,前者只是出售操作。在单元测试中,我使用了此提供程序的子类,该子类将操作和处理程序存储在两个字典中,可以在查询中查询它们然后将其触发。

typealias UIAlertActionHandler = (UIAlertAction) -> Void

protocol UIAlertActionProviderType {
    func makeAlertAction(type: UIAlertActionProvider.ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction
}

具体对象(输入了标题以便以后检索)

class UIAlertActionProvider: UIAlertActionProviderType {
    enum ActionTitle: String {
        case proceed = "Proceed"
        case cancel = "Cancel"
    }

    func makeAlertAction(title: ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction {
        let style: UIAlertAction.Style
        switch title {
        case .proceed: style = .destructive
        case .cancel: style = .cancel
        }

        return UIAlertAction(title: title.rawValue, style: style, handler: handler)
    }
}

单元测试子类(存储以ActionTitle枚举为键的操作和处理程序):

class MockUIAlertActionProvider: UIAlertActionProvider {
    var handlers: [ActionTitle: UIAlertActionHandler] = [:]
    var actions: [ActionTitle: UIAlertAction] = [:]

    override func makeAlertAction(title: ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction {
        handlers[title] = handler

        let action = super.makeAlertAction(title: title, handler: handler)
        actions[title] = action

        return action
    }
}

扩展UIAlertAction以便在测试中启用键入的操作标题查找:

extension UIAlertAction {
    var typedTitle: UIAlertActionProvider.ActionTitle? {
        guard let title = title else { return nil }

        return UIAlertActionProvider.ActionTitle(rawValue: title)
    }
}

演示用法的示例测试:

func testDeleteHandlerActionSideEffectTakesPlace() throws {
    let alertActionProvider = MockUIAlertActionProvider()
    let sut = MyViewController(alertActionProvider: alertActionProvider)

    // Do whatever you need to do to get alert presented, then retrieve action and handler
    let action = try XCTUnwrap(alertActionProvider.actions[.proceed])
    let handler = try XCTUnwrap(alertActionProvider.handlers[.proceed])
    handler(action)

    // Assert whatever side effects are triggered in your code by triggering handler
}