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

Swift Package单元测试调用UNUserNotificationCenter.current()崩溃求助

问题原因

崩溃的核心原因是单元测试目标运行在Xcode的Xcode Agents进程中,这个进程的mainBundle并非正常的应用bundle,导致UNUserNotificationCenter.current()初始化时无法找到有效的bundle代理,抛出NSInternalInconsistencyException。这和Swift Package本身无关,是单元测试运行环境的限制。

可行解决方案

1. 完善依赖注入,彻底避免测试环境调用UNUserNotificationCenter.current()

你的方案1方向正确,但问题出在初始化方法的默认参数会在测试环境触发UNUserNotificationCenter.current()调用。修改初始化逻辑,确保仅生产环境才会初始化真实的通知中心:

import UserNotifications

protocol UserNotificationCenterProtocol {
    func requestAuthorization(options: UNAuthorizationOptions, completionHandler: @escaping (Bool, Error?) -> Void)
    // 按需添加其他需要的方法,比如getNotificationSettings等
}

extension UNUserNotificationCenter: UserNotificationCenterProtocol {}

class NotificationCenterManager {
    private let userNotificationCenter: UserNotificationCenterProtocol

    // 调整初始化逻辑:仅当未传入实例时,在生产环境初始化真实对象
    init(userNotificationCenter: UserNotificationCenterProtocol? = nil) {
        if let center = userNotificationCenter {
            self.userNotificationCenter = center
        } else {
            // 这段代码只会在生产环境执行,测试时传入Mock,不会走到这里
            self.userNotificationCenter = UNUserNotificationCenter.current()
        }
    }

    // 示例业务方法
    func requestNotificationPermission() {
        userNotificationCenter.requestAuthorization(options: [.alert, .badge]) { granted, error in
            // 处理授权结果
        }
    }
}

class MockUserNotificationCenter: UserNotificationCenterProtocol {
    // 记录调用情况,方便断言
    var didCallRequestAuthorization = false
    var capturedOptions: UNAuthorizationOptions?

    func requestAuthorization(options: UNAuthorizationOptions, completionHandler: @escaping (Bool, Error?) -> Void) {
        didCallRequestAuthorization = true
        capturedOptions = options
        // 模拟授权成功
        completionHandler(true, nil)
    }
}

测试代码示例

import XCTest
@testable import YourPackageName

class NotificationCenterManagerTests: XCTestCase {
    func testRequestNotificationPermission() {
        // 初始化Mock并注入
        let mockCenter = MockUserNotificationCenter()
        let manager = NotificationCenterManager(userNotificationCenter: mockCenter)
        
        // 执行测试方法
        manager.requestNotificationPermission()
        
        // 断言调用情况
        XCTAssertTrue(mockCenter.didCallRequestAuthorization)
        XCTAssertEqual(mockCenter.capturedOptions, [.alert, .badge])
    }
}

这样修改后,测试代码完全不会触发UNUserNotificationCenter.current()的调用,从根源上避免崩溃。

2. 备选方案:在测试Setup中临时替换mainBundle(不推荐)

如果因特殊原因无法使用依赖注入,可以尝试在测试的setUp方法中临时替换Bundle.main为有效应用bundle路径,但这种方式依赖环境变量,稳定性差,不推荐:

override func setUp() {
    super.setUp()
    // 替换为测试目标的bundle路径
    let testBundle = Bundle(for: NotificationCenterManagerTests.self)
    object_setClass(Bundle.main, testBundle.classForCoder)
}

override func tearDown() {
    // 恢复原bundle
    object_setClass(Bundle.main, Bundle.self)
    super.tearDown()
}
可行性判断
  • 你的猜测1准确:单元测试的运行环境(Xcode Agents)没有有效的应用bundle,导致UNUserNotificationCenter无法初始化;猜测2不准确,Swift Package在应用环境中可正常使用通知中心,仅单元测试环境存在限制。
  • 依赖注入+Mock是最稳定、符合测试规范的解决方案,完全可以解决问题。

内容的提问来源于stack exchange,提问作者Javier Landa-Torres

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 16:32:42