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

如何在单元测试中触发NSFileCoordinator错误并验证代码逻辑?

解决NSFileCoordinator异步接口单元测试中模拟协调错误的问题

方法1:通过自定义NSFilePresenter触发准备阶段错误

NSFileCoordinator文档明确说明,关联的文件演示器准备写入失败时,会直接返回error且不执行写入回调。你可以手动实现一个抛出错误的文件演示器:

class FailingFilePresenter: NSObject, NSFilePresenter {
    var presentedItemURL: URL?
    var presentedItemOperationQueue: OperationQueue = .main
    
    func prepareForWriting(at url: URL) throws {
        // 抛出自定义错误,触发NSFileCoordinator返回错误
        throw NSError(domain: NSCocoaErrorDomain, code: NSFileWriteUnknownError, userInfo: [NSLocalizedDescriptionKey: "模拟演示器准备失败"])
    }
}

在单元测试中关联该演示器到目标URL:

let testURL = // 你的测试文件URL
let failingPresenter = FailingFilePresenter()
failingPresenter.presentedItemURL = testURL
NSFileCoordinator.addFilePresenter(failingPresenter)

// 调用异步coordinateWriting方法,此时应触发协调错误
do {
    try await yourCoordinateWritingMethod(at: testURL)
    XCTFail("预期抛出协调错误,但未抛出")
} catch {
    XCTAssertEqual((error as NSError).code, NSFileWriteUnknownError)
}

// 测试结束后移除演示器
NSFileCoordinator.removeFilePresenter(failingPresenter)

方法2:通过文件系统权限触发系统级协调错误

修改测试文件的权限,让NSFileCoordinator无法完成协调流程:

let testURL = // 你的测试文件URL
let fileManager = FileManager.default

// 设置文件为只读权限
try fileManager.setAttributes([.posixPermissions: 0o444], ofItemAtPath: testURL.path)

// 调用异步方法,NSFileCoordinator会因无法获取写入权限返回错误
do {
    try await yourCoordinateWritingMethod(at: testURL)
    XCTFail("预期抛出权限错误,但未抛出")
} catch {
    XCTAssertEqual((error as NSError).code, NSFileWriteNoPermissionError)
}

// 测试后恢复文件权限
try fileManager.setAttributes([.posixPermissions: 0o644], ofItemAtPath: testURL.path)

方法3:子类化NSFileCoordinator直接模拟错误

如果不需要真实文件系统交互,可以子类化NSFileCoordinator直接返回错误:

class MockFailingFileCoordinator: NSFileCoordinator {
    override func coordinateWritingItem(at url: URL, options: NSFileCoordinator.WritingOptions, error: NSErrorPointer, byAccessor accessor: @escaping (URL) -> Void) {
        // 直接赋值error,不执行accessor回调
        error?.pointee = NSError(domain: NSCocoaErrorDomain, code: NSFileWriteInvalidFileNameError, userInfo: nil)
    }
}

在你的异步接口中加入依赖注入,允许传入自定义NSFileCoordinator实例:

// 修改异步方法支持注入协调器
func coordinateWriting(at url: URL, coordinator: NSFileCoordinator = NSFileCoordinator()) async throws {
    try await withCheckedThrowingContinuation { continuation in
        var coordinatorError: NSError?
        coordinator.coordinateWritingItem(at: url, options: .forReplacing, error: &coordinatorError) { writingURL in
            // 写入逻辑
            continuation.resume(returning: ())
        }
        if let error = coordinatorError {
            continuation.resume(throwing: error)
        }
    }
}

// 单元测试中使用Mock类
let mockCoordinator = MockFailingFileCoordinator()
do {
    try await coordinateWriting(at: testURL, coordinator: mockCoordinator)
    XCTFail("预期抛出模拟错误,但未抛出")
} catch {
    XCTAssertEqual((error as NSError).code, NSFileWriteInvalidFileNameError)
}

关于CheckedContinuation的安全处理

为避免重复resume,确保在withCheckedThrowingContinuation中只执行一次resume:

  • 不要同时在coordinator的error分支和accessor回调中调用resume
  • 可通过标志位跟踪是否已resume,确保逻辑原子性

优化后的异步方法示例:

func coordinateWriting(at url: URL) async throws {
    try await withCheckedThrowingContinuation { continuation in
        var didResume = false
        let resumeOnce: (Result<Void, Error>) -> Void = { result in
            guard !didResume else { return }
            didResume = true
            switch result {
            case .success:
                continuation.resume(returning: ())
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
        
        var coordinatorError: NSError?
        NSFileCoordinator().coordinateWritingItem(at: url, options: .forReplacing, error: &coordinatorError) { writingURL in
            // 执行写入逻辑,写入失败则调用resumeOnce(.failure(error))
            resumeOnce(.success(()))
        }
        
        if let error = coordinatorError {
            resumeOnce(.failure(error))
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 01:36:58