💯
TestDispatcher完全に理解した
TL;DR
- プロダクションコードのDispatcherはとりあえず全部DIにしとこう
- 直列処理をテストしたいとき:
runTest(UnconfinedTestDispatcher())
- 並列処理をテストしたいとき:
runTest
+StandardTestDispatcher
の注入
説明
DispatcherはDIしよう
class DispatcherModule {
fun provideIODispatcher: CoroutineDispatcher = Dispatchers.IO
}
class HogeRepository(
private val dispatcher: CoroutineDispatcher,
) {
suspend fun fetch() = withContext(dispatcher) {
// データ取得処理
}
}
※DIの書き方は使用するDIライブラリによります。上記は雰囲気実装です。
Android Developersには「CoroutineContext
をDIするともっと柔軟になるのでよいよ」と書いてあったのですが、そこまでの必要性に私は直面したことがないので、まあCoroutineDispatcher
のDIで十分かなと思ってます。
but you can also inject the broader CoroutineContext type, which allows for even more flexibility during tests.
直列処理のテスト
class HogeRepositoryTest {
private lateinit var sut: HogeRepository
@BeforeTest
fun setUp() {
sut = HogeRepository(
dispatcher = Dispatchers.IO // ここに入れるDispatcherはなんでもいい
)
}
@Test
fun testFetch() = runTest(UnconfinedTestDispatcher()) {
val actual = sut.fetch()
// 検証
}
}
UnconfinedTestDispatcher
は、その内部でのスレッド切り替えを抑制するような働きをするため、runTestに渡してしまえば、dispatcherのDI部分を気にしなくてよくなります。
並列処理のテスト
class HogeRepositoryTest {
@Test
fun testFetch() = runTest {
val sut = HogeRepository(
dispatcher = StandardTestDispatcher(testScheduler),
)
val actual = sut.fetch()
// 検証
}
}
UnconfinedTestDispatcher
は実際のスレッド管理に忠実ではないので、並列処理のテストをやらせるべきではありません。
そこで、並列処理のテストではStandardTestDispatcher
を使用します。runTest
の第一引数を省略するとStandardTestDispatcher
が使用されます。
ただし、StandardTestDispatcher
はスレッド切り替えを抑制しないので、DI部分からもStandardTestDispatcher
を注入してやる必要があります。そうしないと、テストが不安定になってしまいます。
おわりに
この記事は、Android Developersに書いてあることを自分なりに解釈したものです。
もし間違い等あったらコメントいただけると助かります!
Discussion