Repository: Automattic/pocket-casts-android
File: modules/services/repositories/src/test/java/au/com/shiftyjelly/pocketcasts/repositories/categories/CategoriesManagerTest.kt (Kotlin)

Code before:

import app.cash.turbine.test
import au.com.shiftyjelly.pocketcasts.models.db.dao.UserCategoryVisitsDao
import au.com.shiftyjelly.pocketcasts.models.entity.UserCategoryVisits
import au.com.shiftyjelly.pocketcasts.repositories.lists.ListRepository
import au.com.shiftyjelly.pocketcasts.servers.model.DiscoverCategory
import au.com.shiftyjelly.pocketcasts.sharedtest.InMemoryFeatureFlagRule
import au.com.shiftyjelly.pocketcasts.sharedtest.MainCoroutineRule
import au.com.shiftyjelly.pocketcasts.utils.featureflag.Feature
import au.com.shiftyjelly.pocketcasts.utils.featureflag.FeatureFlag
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

internal class CategoriesManagerTest {
    @get:Rule
    val featureFlagRule = InMemoryFeatureFlagRule()

    @get:Rule
    val coroutineRule = MainCoroutineRule()

    private val listRepository = mock<ListRepository>()
    private val userCategoryVisitsDao = mock<UserCategoryVisitsDao>()

    companion object {
        val testCategories = List(10) { DiscoverCategory(id = it, name = "name$it", icon = "", source = "") }
    }

    @Test
    fun `GIVEN no visits WHEN categories evaluated THEN popular categories are returned`() = runBlocking {
        FeatureFlag.setEnabled(Feature.SMART_CATEGORIES, true)
        whenever(listRepository.getCategoriesList(any())).thenReturn(testCategories)
        whenever(userCategoryVisitsDao.getCategoryVisitsOrdered()).thenReturn(emptyList())

        val categoriesManager = CategoriesManager(listRepository, userCategoryVisitsDao, CoroutineScope(coroutineRule.testDispatcher))
        categoriesManager.loadCategories("whatever")
        val popularCategoryIds = (0 until 6).toList()
        categoriesManager.setRowInfo(popularCategoryIds = popularCategoryIds, sponsoredCategoryIds = listOf(4, 5, 6, 7))

        categoriesManager.state.test {
            val state = awaitItem()
            assert(state is CategoriesManager.State.Idle)
            assertEquals(testCategories.filter { popularCategoryIds.contains(it.id) }.map { it.id }, (state as CategoriesManager.State.Idle).featuredCategories.map { it.id })
        }
    }

Review comment (suggestion):
I'm not sure I understand the need for this rule. This rule is generally necessary for `ViewModel`s because of the problematic `viewModelScope` property, which enforces `Dispatchers.Main`. 

In this case, however, the rule doesn't help on its own with the test It would be simpler to just use:

```kotlin
private val scope = CoroutineScope(UnconfinedTestDispatcher())

@After
fun tearDown() {
  scope.cancel()
}
```

Code after:

import app.cash.turbine.test
import au.com.shiftyjelly.pocketcasts.models.db.dao.UserCategoryVisitsDao
import au.com.shiftyjelly.pocketcasts.models.entity.UserCategoryVisits
import au.com.shiftyjelly.pocketcasts.repositories.lists.ListRepository
import au.com.shiftyjelly.pocketcasts.servers.model.DiscoverCategory
import au.com.shiftyjelly.pocketcasts.sharedtest.InMemoryFeatureFlagRule
import au.com.shiftyjelly.pocketcasts.utils.featureflag.Feature
import au.com.shiftyjelly.pocketcasts.utils.featureflag.FeatureFlag
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

@OptIn(ExperimentalCoroutinesApi::class)
internal class CategoriesManagerTest {
    @get:Rule
    val featureFlagRule = InMemoryFeatureFlagRule()

    private val listRepository = mock<ListRepository>()
    private val userCategoryVisitsDao = mock<UserCategoryVisitsDao>()
    private val coroutineScope = CoroutineScope(UnconfinedTestDispatcher())

    companion object {
        val testCategories = List(10) { DiscoverCategory(id = it, name = "name$it", icon = "", source = "") }
    }

    @After
    fun tearDown() {
        coroutineScope.cancel()
    }

    @Test
    fun `GIVEN no visits WHEN categories evaluated THEN popular categories are returned`() = runBlocking {
        FeatureFlag.setEnabled(Feature.SMART_CATEGORIES, true)
        whenever(listRepository.getCategoriesList(any())).thenReturn(testCategories)
        whenever(userCategoryVisitsDao.getCategoryVisitsOrdered()).thenReturn(emptyList())

        val categoriesManager = CategoriesManager(listRepository, userCategoryVisitsDao, coroutineScope)
        categoriesManager.loadCategories("whatever")
        val popularCategoryIds = (0 until 6).toList()
        categoriesManager.setRowInfo(popularCategoryIds = popularCategoryIds, sponsoredCategoryIds = listOf(4, 5, 6, 7))