如何测试在 UIAlertController 的完成处理程序中调用的方法?

How can I test the method called in the completion handler of a UIAlertController?

我有一个附加到 UIViewController 的协议,我希望允许显示 UIAlertController

import UIKit

struct AlertableAction {
    var title: String
    var style: UIAlertAction.Style
    var result: Bool
}

protocol Alertable {
    func presentAlert(title: String?, message: String?, actions: [AlertableAction], completion: ((Bool) -> Void)?)
}

extension Alertable where Self: UIViewController {
    func presentAlert(title: String?, message: String?, actions: [AlertableAction], completion: ((Bool) -> Void)?) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
        actions.forEach { action in
            alertController.addAction(UIAlertAction(title: action.title, style: action.style, handler: { _ in completion?(action.result) }))
        }
        present(alertController, animated: true, completion: nil)
    }
}

然后我只要在我的 UIViewController 中调用这个方法,只要我想显示一个警报

   self?.presentAlert(
        title: nil, message: "Are you sure you want to logout?",
        actions: [
            AlertableAction(title: "No", style: .cancel, result: false),
            AlertableAction(title: "Yes", style: .destructive, result: true)],
        completion: { result in
            guard result else { return }
            self?.viewModel.revokeSession()
        }
    )

我试图在 XCTestCase 中断言单击 Yes 会在我的视图模型上调用正确的方法。

我知道 UITest 将允许我测试警报是否可见,然后可能会在点击 我被重定向到注销路线,但我真的有兴趣测试方法本身。

不过我不确定如何在代码中对此进行测试。

I am trying to assert within a XCTestCase that clicking Yes calls the correct method on my view model ... I am really interested in testing the method itself.

目前还不清楚你到底想测试什么。弄清楚(究竟什么需要测试?)是大部分的战斗。您知道当标题为 Yes 时 resulttrue,因此无需测试任何有关此特定警报的实际点击的信息。也许你要测试的只是这个:

    { result in
        guard result else { return }
        self?.viewModel.revokeSession()
    }

换句话说,您想知道当 resulttrue 时会发生什么,而当它是 false 时会发生什么。如果是这种情况,只需将匿名函数替换为真实函数(方法)即可:

func revokeIfTrue(_ result:Bool) {
    guard result else { return }
    self?.viewModel.revokeSession()
}

并重写您的 presentAlert 以将该方法作为其完成:

self?.presentAlert(
    title: nil, message: "Are you sure you want to logout?",
    actions: [
        AlertableAction(title: "No", style: .cancel, result: false),
        AlertableAction(title: "Yes", style: .destructive, result: true)],
    completion: revokeIfTrue
)

现在您已将函数分解为可独立测试的部分。

使用ViewControllerPresentationSpy,你的测试可以说

let alertVerifier = AlertVerifier()

创建验证器。然后调用您的 presentAlert 函数。接下来,调用

alertVerifier.executeAction(forButton: "Yes")

执行给定的操作。最后,调用捕获的闭包:

alertVerifier.capturedCompletion?()

并验证预期结果。