测试 UIAlertController 是否已经出现

Testing if UIAlertController has been presented

我有一个协议可以让我的 ViewController 显示警报。

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 generator = UIImpactFeedbackGenerator(style: .medium)
        generator.impactOccurred()
        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)
    }
}

我称之为

   @objc private func didTapLogout() {
        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: { [weak self] result in
                guard result else { return }
                self?.presenter.logout()
            }
        )
    }

我想编写一个单元测试来断言调用它时,呈现的视图控制器是 UIAlertController

我正在尝试类似的方法,但没有通过

    func test_renders_alert_controller() {
        sut.show()

        XCTAssertNotNil(sut.presentedViewController)
    }

    class MockViewController: UIViewController, Alertable {

        var presentViewControllerTarget: UIViewController?

        func show() {
            presentAlert(title: nil, message: "Are you sure you want to logout?", actions:
                [AlertableAction(title: "No", style: .cancel, result: false)],
                completion: nil
            )

            self.presentViewControllerTarget = self.presentedViewController
        }
    }

您需要等待 UIAlertController 完全可见才能 运行 您的断言。

查看 XCTWaiter

试试下面的方法:

    let nav = UINavigationController.init(rootViewController: sut)

    sut.show()

    let exp = expectation(description: "Test after 1.5 second wait")
    let result = XCTWaiter.wait(for: [exp], timeout: 1.5)
    if result == XCTWaiter.Result.timedOut {
        XCTAssertNotNil(nav.visibleViewController is UIAlertController)
    } else {
        XCTFail("Delay interrupted")
    }

ViewControllerPresentationSpy 通过捕获 用于显示警报的信息,而不实际显示任何警报,从而避免缓慢、不稳定的单元测试。您只需要创建一个 AlertVerifier,然后调用显示您的警报的任何内容:

let alertVerifier = AlertVerifier()

sut.show()

alertVerifier.verify(
    title: nil,
    message: "Are you sure you want to logout?",
    animated: true,
    presentingViewController: sut,
    actions: [
        .cancel("No"),
        .destructive("Yes"),
    ]
)

此验证方法检查:

  • 显示了一个带有动画的警报。
  • 呈现视图控制器是被测系统。
  • 警报标题。
  • 警报消息。
  • UIAlertController.Style的首选样式(默认.alert
  • 每个动作的标题和样式。

您可以按名称调用每个操作:

try alertVerifier.executeAction(forButton: "Yes")

(将测试标记为 throws。如果没有具有给定名称的按钮,测试将失败。)

试试看它与 1.5 秒超时相比有多快。还要比较你能考多少。