帮助 Swift 开发者用协议依赖注入编写可测试代码并高效构建 Mock。
复制安装指令,让 AI 自动完成配置 · 推荐新手
请帮我安装 askskill 上的 "swift-protocol-di-testing" 技能: 1. 下载 https://raw.githubusercontent.com/affaan-m/ECC/main/skills/swift-protocol-di-testing/SKILL.md 2. 保存为 ~/.claude/skills/swift-protocol-di-testing/SKILL.md 3. 装好后重载技能,告诉我可以用了
请用 Swift 展示如何把文件读取逻辑从具体 FileManager 实现中解耦,定义一个精简协议并注入依赖,再用 Swift Testing 编写单元测试,包含一个成功读取和一个读取失败的 mock 示例。
输出基于协议的文件系统抽象、可注入的业务代码,以及使用 mock 验证成功与失败场景的测试示例。
请设计一个 Swift 网络客户端示例,使用协议抽象请求发送层,让业务逻辑不直接依赖 URLSession。再给出 mock 响应、错误响应和超时场景的 Swift Testing 测试代码。
输出可替换的网络协议接口、生产实现思路,以及覆盖多种响应情况的单元测试代码。
请示范如何在 Swift 中为外部 API 服务定义面向单一职责的协议,并通过依赖注入让 ViewModel 可测试。请给出 mock 服务、ViewModel 实现和对应的 Swift Testing 测试。
输出聚焦职责的 API 协议设计、可测试的 ViewModel 结构,以及基于 mock 的测试示例。
Patterns for making Swift code testable by abstracting external dependencies (file system, network, iCloud) behind small, focused protocols. Enables deterministic tests without I/O.
Each protocol handles exactly one external concern.
// File system access
public protocol FileSystemProviding: Sendable {
func containerURL(for purpose: Purpose) -> URL?
}
// File read/write operations
public protocol FileAccessorProviding: Sendable {
func read(from url: URL) throws -> Data
func write(_ data: Data, to url: URL) throws
func fileExists(at url: URL) -> Bool
}
// Bookmark storage (e.g., for sandboxed apps)
public protocol BookmarkStorageProviding: Sendable {
func saveBookmark(_ data: Data, for key: String) throws
func loadBookmark(for key: String) throws -> Data?
}
public struct DefaultFileSystemProvider: FileSystemProviding {
public init() {}
public func containerURL(for purpose: Purpose) -> URL? {
FileManager.default.url(forUbiquityContainerIdentifier: nil)
}
}
public struct DefaultFileAccessor: FileAccessorProviding {
public init() {}
public func read(from url: URL) throws -> Data {
try Data(contentsOf: url)
}
public func write(_ data: Data, to url: URL) throws {
try data.write(to: url, options: .atomic)
}
public func fileExists(at url: URL) -> Bool {
FileManager.default.fileExists(atPath: url.path)
}
}
public final class MockFileAccessor: FileAccessorProviding, @unchecked Sendable {
public var files: [URL: Data] = [:]
public var readError: Error?
public var writeError: Error?
public init() {}
public func read(from url: URL) throws -> Data {
if let error = readError { throw error }
guard let data = files[url] else {
throw CocoaError(.fileReadNoSuchFile)
}
return data
}
public func write(_ data: Data, to url: URL) throws {
if let error = writeError { throw error }
files[url] = data
}
public func fileExists(at url: URL) -> Bool {
files[url] != nil
}
}
Production code uses defaults; tests inject mocks.
public actor SyncManager {
private let fileSystem: FileSystemProviding
private let fileAccessor: FileAccessorProviding
public init(
fileSystem: FileSystemProviding = DefaultFileSystemProvider(),
fileAccessor: FileAccessorProviding = DefaultFileAccessor()
) {
self.fileSystem = fileSystem
self.fileAccessor = fileAccessor
}
public func sync() async throws {
guard let containerURL = fileSystem.containerURL(for: .sync) else {
throw SyncError.containerNotAvailable
}
let data = try fileAccessor.read(
from: containerURL.appendingPathComponent("data.json")
)
// Process data...
}
}
import Testing
@Test("Sync manager handles missing container")
func testMissingContainer() async {
let mockFileSystem = MockFileSystemProvider(containerURL: nil)
let manager = SyncManager(fileSystem: mockFileSystem)
await #expect(throws: SyncError.containerNotAvailable) {
try await manager.sync()
}
}
@Test("Sync manager reads data correctly")
func testReadData() async throws {
let mockFileAccessor = MockFileAccessor()
…
使用 MockDeviceKit 在无实体眼镜硬件时进行功能联调与测试验证