如何在iOS UITest(XCTest)中模拟来电类中断场景
XCTest 模拟来电类系统中断的落地方案
首先明确前提:非越狱iOS环境下,受沙盒权限限制,测试代码无法直接触发运营商真实蜂窝来电,但来电对App的影响本质是高优先级系统弹窗抢占前台、App切换到inactive状态、中断当前操作流、事件结束后App返回前台恢复状态,完全可以通过等效模拟覆盖测试场景,以下是三类可直接落地的方案,适配不同测试需求:
- 方案1:XCTest中断监控+CallKit模拟来电(首推,无生产代码侵入,CI可跑)
这个方案触发的弹窗UI、App生命周期回调和真实来电完全一致,不需要修改主工程代码,仅需要在UITest target引入CallKit依赖即可。
核心实现代码:
实测从iOS 14到iOS 17所有正式版本都能稳定运行,不需要额外权限。func testIncomingCallInterrupt() { // 注册系统中断监控 let interruptExpectation = expectation(description: "Wait incoming call interrupt handled") addUIInterruptionMonitor(withDescription: "System call alert") { alert in // 模拟来电响铃3秒,对齐真实用户接到来电的等待时长 sleep(3) // 点击拒绝模拟来电挂断,回到App if alert.buttons["拒绝"].exists { alert.buttons["拒绝"].tap() } interruptExpectation.fulfill() return true } // 触发CallKit模拟来电,弹出和真实来电完全一致的系统级弹窗 let callController = CXCallController() let callAction = CXStartCallAction( call: UUID(), handle: CXHandle(type: .phoneNumber, value: "10000") ) callAction.contactIdentifier = "测试来电" callController.request(CXTransaction(action: callAction)) { _ in } // 必须做一次App交互,才能让XCTest捕获到跨App的系统弹窗 app.tap() // 等待中断处理完成 wait(for: [interruptExpectation], timeout: 10) // 这里写恢复后的断言:比如校验音视频流是否恢复、编辑的内容是否丢失、页面导航状态是否正常 XCTAssertTrue(app.staticTexts["首页"].exists) } - 方案2:越狱设备真实来电触发(仅适合本地专项验证)
如果必须覆盖真实蜂窝来电的场景,可以用越狱测试机,安装Activator插件后,通过SSH连接设备执行指令触发真实来电:
这个方案无法在CI流水线部署,且越狱环境和普通用户环境存在差异,仅建议作为补充验证手段,不要作为核心测试方案。activator send com.apple.mobilephone.incomingcall - 方案3:生命周期模拟(适合逻辑层快速校验)
如果不需要验证系统弹窗遮挡的交互,只需要验证App中断后的状态恢复逻辑,可以直接模拟App状态切换,实现成本最低:// 模拟App被系统事件抢占进入inactive状态,和来电触发的生命周期回调完全一致 XCUIDevice.shared.press(.home) sleep(3) app.activate() // 后续执行状态恢复的断言
注意:不要尝试调用私有API触发真实来电,这类代码不仅存在App Store审核风险,不同iOS版本的私有API兼容性极差,长期维护成本极高。上述等效模拟方案已经可以覆盖99%的中断容错测试场景,完全满足上线验收要求。
内容的提问来源于stack exchange,提问作者Sven Tan
相关产品推荐
相关产品推荐

