第 12 课2026.07.28
如果一个类在内部直接创建 Ktor Client 或 Repository,它会与具体实现紧密绑定,测试时也必须访问真实网络。构造器依赖注入让外部决定使用真实实现还是测试替身,Android 和 iOS 也能按各自入口组装同一套共享业务。
第 12 课:用构造器注入共享依赖
- 日期:2026-07-28
- 课程序号:12
- 知识点:通过接口与构造器传入依赖,使共享代码可替换、可测试
用途
如果一个类在内部直接创建 Ktor Client 或 Repository,它会与具体实现紧密绑定,测试时也必须访问真实网络。构造器依赖注入让外部决定使用真实实现还是测试替身,Android 和 iOS 也能按各自入口组装同一套共享业务。
核心概念
- 依赖者只面向接口,不直接创建具体实现。
- 依赖通过构造器传入,类的必需条件一目了然。
- 生产环境传入真实 Repository,测试环境传入 Fake。
- 先掌握手动注入;项目复杂后再评估是否需要 DI 框架。
示例代码
先定义共享层依赖的接口:
kotlin
interface UserRepository {
suspend fun loadUser(id: Long): DataResult<User>
}用例通过构造器接收接口,并负责业务需要的转换:
kotlin
class GetUserLabel(
private val repository: UserRepository,
) {
suspend operator fun invoke(id: Long): DataResult<String> {
return when (val result = repository.loadUser(id)) {
is DataResult.Success -> {
DataResult.Success(result.value.displayName)
}
is DataResult.Failure -> result
}
}
}生产代码可以传入真实实现:
kotlin
val repository: UserRepository = RealUserRepository(remoteDataSource)
val getUserLabel = GetUserLabel(repository)测试时传入不访问网络的 Fake:
kotlin
class FakeUserRepository(
private val result: DataResult<User>,
) : UserRepository {
override suspend fun loadUser(id: Long): DataResult<User> {
return result
}
}测试使用 runTest 时,需要在 commonTest 加入协程测试依赖:
kotlin
commonTest.dependencies {
implementation(libs.kotlinx.coroutines.test)
}commonTest/GetUserLabelTest.kt:
kotlin
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
class GetUserLabelTest {
@Test
fun returnsDisplayNameFromRepository() = runTest {
val repository = FakeUserRepository(
DataResult.Success(
User(id = 1L, displayName = "Ada"),
),
)
val useCase = GetUserLabel(repository)
val result = useCase(1L)
assertEquals(DataResult.Success("Ada"), result)
}
}测试只验证共享业务,不依赖服务器、Android SDK 或 iOS API。
5 分钟练习
新增一个测试:让 FakeUserRepository 返回 DataError.Network,验证 GetUserLabel 原样保留失败结果。
参考答案
kotlin
@Test
fun keepsRepositoryFailure() = runTest {
val repository = FakeUserRepository(
DataResult.Failure(DataError.Network),
)
val useCase = GetUserLabel(repository)
val result = useCase(1L)
assertEquals(
DataResult.Failure(DataError.Network),
result,
)
}用例没有猜测或改写错误来源,因此数据层归类的失败语义可以沿调用链稳定传递。
与上一课的联系
上一课定义了 DataResult 来表达成功和失败;本课通过构造器注入 Repository,并用 Fake 同时验证这两个结果分支,为后续组装完整共享业务做好准备。