HiltでDispatcherをInjectしているClassをTestする

CoroutineでDispatcherを使う場合、直接指定するわけではなくInjectすることが推奨されています。
GoogleのSampleでは以下のコードが記載されていますが、HiltをつかってInjectしている場合、初期値を使えないのでこのままでは動かせません。

```kotlin
// DO inject Dispatchers
class NewsRepository(
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    suspend fun loadNews() = withContext(defaultDispatcher) { /* ... */ }
}

// DO NOT hardcode Dispatchers
class NewsRepository {
    // DO NOT use Dispatchers.Default directly, inject it instead
    suspend fun loadNews() = withContext(Dispatchers.Default) { /* ... */ }
}
```

なので、以下のようなModuleを使ってあげるとAnnotationを使って簡単に使用したいDispatcherをInjectすることができます。

```kotlin

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class DefaultDispatcher

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class MainDispatcher

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class UnconfinedDispatcher

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class IoDispatcher

@Module
class DispatcherModule {

    @DefaultDispatcher
    @Provides
    fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default

    @MainDispatcher
    @Provides
    fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main

    @UnconfinedDispatcher
    @Provides
    fun provideUnconfinedDispatcher(): CoroutineDispatcher = Dispatchers.Unconfined

    @IoDispatcher
    @Provides
    fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
}


class NewsRepository @Inject constructor(
    @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) {
    suspend fun loadNews() = withContext(defaultDispatcher) { /* ... */ }
}
```

さて、このクラスをテストする場合、GoogleのSampleでは以下のコードが記載されています。

```kotlin
class ArticlesRepositoryTest {

    @Test
    fun testBookmarkArticle() = runTest {
        // Pass the testScheduler provided by runTest's coroutine scope to
        // the test dispatcher
        val testDispatcher = UnconfinedTestDispatcher(testScheduler)

        val articlesDataSource = FakeArticlesDataSource()
        val repository = ArticlesRepository(
            articlesDataSource,
            testDispatcher
        )
        val article = Article()
        repository.bookmarkArticle(article)
        assertThat(articlesDataSource.isBookmarked(article)).isTrue()
    }
}
```

しかし、この動かし方だと、毎回テストするクラスとDispatcherを作る必要がありとても面倒です。
なので、Test用のDispatcherを最初にInjectして動かせば良さそうですが、エラーが出て動きません。

```kotlin
class ArticlesRepositoryTest {

    private val testDispatcher = UnconfinedTestDispatcher(testScheduler)

    private val repository = ArticlesRepository(
        articlesDataSource,
        testDispatcher
    )

    @Test
    fun testBookmarkArticle() = runTest {
        val article = Article()
        repository.bookmarkArticle(article)
        assertThat(articlesDataSource.isBookmarked(article)).isTrue()
    }
}
```

> Detected use of different schedulers. If you need to use several test coroutine dispatchers, create one `TestCoroutineScheduler` and pass it to each of them

これはrunTestで別のDispatcherで動いているためエラーとなるようです。
なので、runTest時に動かすDispatcherを指定してあげることで解消されます。

```kotlin
class ArticlesRepositoryTest {

    private val testDispatcher = UnconfinedTestDispatcher(testScheduler)

    private val repository = ArticlesRepository(
        articlesDataSource,
        testDispatcher
    )

    @Test
    fun testBookmarkArticle() = runTest(testDispatcher) {
        val article = Article()
        repository.bookmarkArticle(article)
        assertThat(articlesDataSource.isBookmarked(article)).isTrue()
    }
}
```

これでいい感じにテストができるようになりました。良かったですね。

# 参考

https://developer.android.com/kotlin/coroutines/coroutines-best-practices