2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-11-08

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

// 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することができます。


@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では以下のコードが記載されています。

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して動かせば良さそうですが、エラーが出て動きません。

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を指定してあげることで解消されます。

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()
    }
}

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

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?