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

如何对在视图控制器外调用rootViewController.present的函数做单元测试?

解决视图弹出函数单元测试的Completion类型不匹配问题

嘿,我来帮你搞定这个单元测试里的视图弹出函数问题!你遇到的错误本质很明确:UIViewController.present(_:animated:completion:)要求的completion是不带参数的闭包((() -> Void)?),但你自己定义的函数completion是带Bool参数的((Bool) -> Void)?,两者类型不兼容,所以编译器直接报错了。下面给你几个针对性的解决方案,结合单元测试场景来拆解:

方案1:直接调整函数的Completion类型(最简洁)

如果你的弹出函数不需要通过completion传递Bool结果(只是通知弹出完成),那直接把completion改成和系统present一致的类型即可:

// 修改后的弹出函数
func presentTargetVC(from rootVC: UIViewController?, completion: (() -> Void)? = nil) {
    let targetVC = YourTargetViewController()
    rootVC?.present(targetVC, animated: true, completion: completion)
}

对应的单元测试可以用XCTestExpectation来验证completion是否被触发,同时验证弹出的VC是否正确:

func testPresentTargetVC_Success() {
    // 创建测试期望,用于等待completion回调
    let completionExpectation = XCTestExpectation(description: "Present completion triggered")
    let mockRootVC = UIViewController()
    
    // 调用弹出函数
    presentTargetVC(from: mockRootVC) {
        completionExpectation.fulfill()
    }
    
    // 等待回调触发,超时时间1秒足够UI操作完成
    wait(for: [completionExpectation], timeout: 1.0)
    
    // 额外验证:确认rootVC确实弹出了目标VC
    XCTAssertTrue(mockRootVC.presentedViewController is YourTargetViewController)
}

方案2:保留Bool参数的Completion(需要传递结果时)

如果你的业务逻辑需要通过completion告知调用者“弹出是否成功”(比如判断rootVC是否存在),那可以在系统present的completion里做一层包装,把Bool结果传递出去:

// 带Bool结果的弹出函数
func presentTargetVC(from rootVC: UIViewController?, completion: ((Bool) -> Void)? = nil) {
    guard let validRootVC = rootVC else {
        // 没有可用的rootVC,直接返回失败
        completion?(false)
        return
    }
    
    let targetVC = YourTargetViewController()
    validRootVC.present(targetVC, animated: true) {
        // present完成后,返回成功状态
        completion?(true)
    }
}

对应的单元测试要分成功和失败两种场景:

func testPresentTargetVC_Success() {
    let expectation = XCTestExpectation(description: "Present success callback triggered")
    let mockRootVC = UIViewController()
    
    presentTargetVC(from: mockRootVC) { success in
        // 验证回调返回成功
        XCTAssertTrue(success)
        expectation.fulfill()
    }
    
    wait(for: [expectation], timeout: 1.0)
    XCTAssertTrue(mockRootVC.presentedViewController is YourTargetViewController)
}

func testPresentTargetVC_Failure() {
    let expectation = XCTestExpectation(description: "Present failure callback triggered")
    
    // 传入nil的rootVC,模拟失败场景
    presentTargetVC(from: nil) { success in
        XCTAssertFalse(success)
        expectation.fulfill()
    }
    
    wait(for: [expectation], timeout: 1.0)
}

方案3:用MockViewController做精准测试(进阶)

如果想更严谨地验证present方法是否被正确调用(而不仅仅依赖completion),可以创建一个MockViewController类来记录present的调用情况:

class MockRootViewController: UIViewController {
    // 记录是否调用了present方法
    var wasPresentCalled = false
    // 记录被弹出的VC实例
    var presentedViewControllerInstance: UIViewController?
    
    override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        wasPresentCalled = true
        presentedViewControllerInstance = viewControllerToPresent
        // 手动触发completion,模拟系统行为
        completion?()
    }
}

然后在单元测试里使用这个Mock类:

func testPresentTargetVC_WithMock() {
    let expectation = XCTestExpectation(description: "Completion callback triggered")
    let mockRootVC = MockRootViewController()
    
    presentTargetVC(from: mockRootVC) { success in
        XCTAssertTrue(success)
        expectation.fulfill()
    }
    
    wait(for: [expectation], timeout: 1.0)
    
    // 验证present方法确实被调用
    XCTAssertTrue(mockRootVC.wasPresentCalled)
    // 验证弹出的是目标VC
    XCTAssertTrue(mockRootVC.presentedViewControllerInstance is YourTargetViewController)
}

小提示:UI操作要在主队列执行

单元测试中涉及UIKit的操作,最好放在主队列执行,避免线程问题。可以把测试代码包在DispatchQueue.main.async里,或者用XCTest的perform(on: .main)方法:

func testPresentTargetVC_OnMainQueue() {
    let expectation = XCTestExpectation(description: "Present completion on main queue")
    
    DispatchQueue.main.async {
        let mockRootVC = UIViewController()
        self.presentTargetVC(from: mockRootVC) {
            expectation.fulfill()
        }
    }
    
    wait(for: [expectation], timeout: 1.0)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:41:33