You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确测试UIAlertController的呈现?断言失败问题排查

如何正确测试UIAlertController是否被ShopViewController成功呈现?

问题描述

我正在测试一个用于呈现UIAlertController的函数,但测试持续失败。该函数定义如下:

func presentBuyingErrorDiaologue() { 
    let alert = UIAlertController(
        title: "Warning",
        message: "Error purchasing item, please retry this action. If that doesn't help, try restarting or reinstalling the app.",
        preferredStyle: .alert
    )
    let okButton = UIAlertAction(title: "OK", style: .default)
    alert.addAction(okButton)
    self.present(alert, animated: true, completion: nil)
}

该函数位于ShopViewController类中,我原本的测试逻辑是调用shopViewController.presentBuyingErrorDiaologue()后,使用XCTAssertTrue(shopViewController.presentedViewController is UIAlertController)进行断言,但运行测试时该断言失败。请问正确测试UIAlertController是否为当前呈现视图的方法是什么?


解决方案

你的测试失败主要有两个核心原因:

  1. present(_:animated:completion:)是异步执行的UI操作,你调用完函数立刻断言时,UIKit还没完成弹窗的呈现流程,此时presentedViewController还未更新。
  2. 测试中的ShopViewController没有被添加到有效的窗口上下文,UIKit无法正常完成present操作。

下面是两种可靠的解决方案:

方案1:利用异步测试+Completion闭包(推荐,可控性更高)

首先稍微修改你的原函数,添加一个可选的completion闭包,这样可以在弹窗完全呈现后触发断言:

func presentBuyingErrorDiaologue(completion: (() -> Void)? = nil) { 
    let alert = UIAlertController(
        title: "Warning",
        message: "Error purchasing item, please retry this action. If that doesn't help, try restarting or reinstalling the app.",
        preferredStyle: .alert
    )
    let okButton = UIAlertAction(title: "OK", style: .default)
    alert.addAction(okButton)
    self.present(alert, animated: true, completion: completion)
}

然后编写测试代码,使用XCTestExpectation来等待异步操作完成:

func testBuyingErrorDialogueIsPresented() {
    // 1. 创建ViewController实例并添加到测试窗口
    let shopVC = ShopViewController()
    let testWindow = UIWindow()
    testWindow.rootViewController = shopVC
    testWindow.makeKeyAndVisible()
    
    // 2. 创建测试期望
    let alertPresentedExpectation = expectation(description: "Buying error alert is presented")
    
    // 3. 调用弹窗函数,在completion里执行断言
    shopVC.presentBuyingErrorDiaologue {
        // 断言当前呈现的是UIAlertController
        XCTAssertTrue(shopVC.presentedViewController is UIAlertController)
        
        // 可选:额外验证弹窗的内容(标题、按钮等)
        if let alert = shopVC.presentedViewController as? UIAlertController {
            XCTAssertEqual(alert.title, "Warning")
            XCTAssertEqual(alert.actions.first?.title, "OK")
            XCTAssertEqual(alert.preferredStyle, .alert)
        }
        
        // 满足测试期望
        alertPresentedExpectation.fulfill()
    }
    
    // 4. 等待异步操作完成,超时时间设为1秒足够UIKit处理
    waitForExpectations(timeout: 1, handler: nil)
}

方案2:等待RunLoop处理UI更新(无需修改原函数)

如果你不想修改原函数,可以通过等待主RunLoop完成一次循环,让UIKit有时间处理弹窗的呈现:

func testBuyingErrorDialogueIsPresented() {
    let shopVC = ShopViewController()
    let testWindow = UIWindow()
    testWindow.rootViewController = shopVC
    testWindow.makeKeyAndVisible()
    
    shopVC.presentBuyingErrorDiaologue()
    
    // 等待主RunLoop处理完当前的UI更新任务
    RunLoop.main.run(until: Date().addingTimeInterval(0.1))
    
    // 此时再执行断言
    XCTAssertTrue(shopVC.presentedViewController is UIAlertController)
}

这种方式的缺点是依赖固定的等待时间,不同设备上可能有差异,不如方案1可靠。


关键注意点

  • 测试中必须将ShopViewController添加到UIWindow并设为可见,否则UIKit没有上下文来呈现弹窗,presentedViewController始终为nil。
  • 优先选择方案1,通过completion闭包来触发断言,这种方式是完全同步于弹窗呈现完成的时机,不会出现超时或提前断言的问题。

内容的提问来源于stack exchange,提问作者Erika

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:33:53