0Pricing
Swift Academy · 课时

基于协议的模拟与依赖注入

通过协议注入测试替身,无需第三方模拟框架。

基于协议的模拟与依赖注入 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。

为什么使用基于协议的模拟?

使用协议作为接缝,可以在不修改生产代码的情况下,将真实实现替换为测试替身。

protocol NetworkService {
  func fetch(url: URL) async throws -> Data
}
class RealNetworkService: NetworkService { /* URLSession */ }
class MockNetworkService: NetworkService { /* returns fixtures */ }

定义协议

从依赖项中提取一个协议,只包含您的类所需的方法。

protocol UserRepository {
  func getUser(id: Int) async throws -> User
  func saveUser(_ user: User) async throws
}

通过初始化器注入

通过初始化器传入依赖项,以便测试可以提供一个 mock。

class ProfileViewModel {
  private let repo: UserRepository
  init(repo: UserRepository = RealUserRepository()) {
    self.repo = repo
  }
}

编写模拟对象

使用可控状态实现该协议:记录调用,并返回桩值。

class MockUserRepo: UserRepository {
  var fetchedIds: [Int] = []
  var stubbedUser: User = User(id: 1, name: "Test")
  func getUser(id: Int) async throws -> User {
    fetchedIds.append(id)
    return stubbedUser
  }
  func saveUser(_ user: User) async throws {}
}

桩返回值

在测试前配置桩,以控制 mock 返回的内容,从而模拟不同的场景。

let mock = MockUserRepo()
mock.stubbedUser = User(id: 42, name: "Alice")
let vm = ProfileViewModel(repo: mock)
await vm.loadProfile(id: 42)
XCTAssertEqual(vm.userName, "Alice")

验证交互

执行被测系统后,断言 mock 是否使用预期的参数进行了调用。

XCTAssertEqual(mock.fetchedIds, [42], "Should fetch user 42 exactly once")

模拟错误

让 mock 抛出错误,以测试生产代码中的错误处理路径。

class FailingRepo: UserRepository {
  func getUser(id: Int) async throws -> User { throw NetworkError.notFound }
  func saveUser(_ user: User) async throws { throw NetworkError.serverError(500) }
}

属性注入

另一种方式是将依赖项公开为可设置的属性,以便在测试中更简单地替换它。

class OrderService {
  var paymentGateway: PaymentGateway = StripeGateway()
}
let service = OrderService()
service.paymentGateway = MockPaymentGateway()

@Environment / @EnvironmentObject 注入

SwiftUI 应用通过环境注入依赖项,因此只需提供模拟的环境对象,就可以测试视图。

let mockStore = MockCartStore()
let view = CartView().environmentObject(mockStore)

避免过度模拟

只模拟直接依赖项;对于行为确定且运行快速的协作者,请使用真实实现。

// OK to use real DateFormatter in tests — it's deterministic
// Mock URLSession — network I/O is slow and non-deterministic

通过协议组合缩小接口

使用更小、更专注的协议(接口隔离原则),这样 mock 只需实现所需的部分。

protocol Readable { func read() async throws -> [Item] }
protocol Writable { func write(_ item: Item) async throws }
typealias ReadWrite = Readable & Writable

快速检查

与注入具体类型相比,通过协议注入依赖项的主要好处是什么?

课程回顾

为每个依赖项定义一个协议接缝。通过初始化器注入依赖项。实现能够记录调用并返回桩值的 mock。使用会抛出错误的 mock 模拟错误。使用 XCTAssertEqual 比较记录的调用数组,以验证交互。

常见问题解答

「基于协议的模拟与依赖注入」课时是免费的吗?

是的 — 「基于协议的模拟与依赖注入」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 4 节课。

「基于协议的模拟与依赖注入」这节课中我会学到什么?

通过协议注入测试替身,无需第三方模拟框架。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Swift Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「基于协议的模拟与依赖注入」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Swift Academy 课中编写并运行代码吗?

能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. XCTestCase 设置、清理与测试方法
  2. XCTAssert 系列与抛出断言
  3. 基于协议的模拟与依赖注入
  4. 异步测试与性能测量
← 返回 Swift Academy