主要实现: 直接调用 suspend 函数的 测试用例.
1. 要测试的代码
TitleRepository.kt
suspend fun refreshTitle() {
try {
// Make network request using a blocking call
val result = network.fetchNextTitle()
titleDao.insertTitle(Title(result))
} catch (cause: Throwable) {
// If anything throws an exception, inform the caller
throw TitleRefreshError("Unable to refresh title", cause)
}
}
2. 测试用例
TitleRepositoryTest.kt
2.1 测试更新标题成功case ( refreshTitle)
@Test
fun whenRefreshTitleSuccess_insertsRows() {
val subject = TitleRepository(
MainNetworkFake("OK"),
TitleDaoFake("title")
)
subject.refreshTitle()
}
如果写出上面这样, 是会有编译报错的。
因此 refreshTitle 是一个 suspend 函数,需要从 协程 或者 另外一个 suspend 函数调用.
但是,这里是测试程序,它不了解协程,因此无法设置为 suspend.
可以使用 CoroutineScope.launch 执行一个协程,不过它是异步代码 且 非阻塞,立即会返回执行后续代码.
因此, 可以使用 kotlinx-coroutines-test 库包含的 runBlockingTest 函数.
该函数会在调用 suspend 函数时 执行 阻塞.
可以将它看作一种将suspend函数和协程 转换为正常函数调用的方式
重要提示:runBlockingTest 函数将始终阻塞调用方,就像常规函数调用一样。
协程将在同一线程上同步运行。
您应避免在应用代码中使用 runBlocking 和 runBlockingTest,而应优先使用会立即返回的 launch。
runBlockingTest 只能在测试中使用,因为它是以测试控制的方式执行协程的,而 runBlocking 可用于为协程提供阻塞接口。
最后,TitleRepositoryTest.kt代码如下:
@Test
fun whenRefreshTitleSuccess_insertsRows() = runBlockingTest {
val titleDao = TitleDaoFake("title")
val subject = TitleRepository(
MainNetworkFake("OK"),
titleDao
)
subject.refreshTitle()
Truth.assertThat(titleDao.nextInsertedOrNull()).isEqualTo("OK")
}
此测试使用提供的模拟对象来验证 refreshTitle 是否已将“OK”插入数据库。
在测试调用 runBlockingTest 时,它将会阻塞,直到由 runBlockingTest 启动的协程完成为止。
然后,在内部,当我们调用 refreshTitle 时,它会使用常规的挂起和恢复机制,以等待数据库行添加到我们的虚拟对象中。
测试协程完成后,runBlockingTest 将返回。
2.2 测试更新标题超时case ( refreshTitle)
向网络请求添加短暂超时
TitleRepositoryTest.kt
@Test(expected = TitleRefreshError::class)
fun whenRefreshTitleTimeout_throws() = runBlockingTest {
val network = MainNetworkCompletableFake()
val subject = TitleRepository(
network,
TitleDaoFake("title")
)
launch {
subject.refreshTitle()
}
advanceTimeBy(5_000)
}
其中,
(1)此测试使用提供的虚构对象 MainNetworkCompletableFake,这是一个网络虚构对象,
用于暂停调用方,直到测试继续执行调用方为止。
当 refreshTitle 尝试发出网络请求时,它会永久挂起,因为我们想要测试超时情况。
(2) 然后,它会启动单独的协程来调用 refreshTitle。
这是测试超时的关键部分,发生超时的协程应与 runBlockingTest 创建的协程不同
(3)advanceTimeBy(5_000) 将时间调快 5s 并使另外一个 协程超时.
需要在 TitleRepository.refreshTitle 添加超时
suspend fun refreshTitle() {
try {
// Make network request using a blocking call
val result = withTimeout(5_000) {
network.fetchNextTitle()
}
titleDao.insertTitle(Title(result))
} catch (cause: Throwable) {
// If anything throws an exception, inform the caller
throw TitleRefreshError("Unable to refresh title", cause)
}
}