diff --git a/core-testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/util/TestDispatcherRule.kt b/core-testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/util/MainDispatcherRule.kt similarity index 54% rename from core-testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/util/TestDispatcherRule.kt rename to core-testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/util/MainDispatcherRule.kt index 789779716..5b8807b75 100644 --- a/core-testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/util/TestDispatcherRule.kt +++ b/core-testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/util/MainDispatcherRule.kt @@ -16,31 +16,27 @@ package com.google.samples.apps.nowinandroid.core.testing.util -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import org.junit.rules.TestRule +import org.junit.rules.TestWatcher import org.junit.runner.Description -import org.junit.runners.model.Statement /** - * A [TestRule] that initializes the main dispatcher to [dispatcher], which defaults to a - * [StandardTestDispatcher]. + * A JUnit [TestRule] that sets the Main dispatcher to [testDispatcher] + * for the duration of the test. */ -class TestDispatcherRule( - private val dispatcher: CoroutineDispatcher = StandardTestDispatcher() -) : TestRule { - override fun apply(base: Statement, description: Description): Statement = - object : Statement() { - override fun evaluate() { - Dispatchers.setMain(dispatcher) - try { - base.evaluate() - } finally { - Dispatchers.resetMain() - } - } - } +class MainDispatcherRule( + val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(), +) : TestWatcher() { + override fun starting(description: Description) { + Dispatchers.setMain(testDispatcher) + } + + override fun finished(description: Description) { + Dispatchers.resetMain() + } } diff --git a/feature-author/src/test/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModelTest.kt b/feature-author/src/test/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModelTest.kt index 9ab24160b..0e1fc48d2 100644 --- a/feature-author/src/test/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModelTest.kt +++ b/feature-author/src/test/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModelTest.kt @@ -17,7 +17,6 @@ package com.google.samples.apps.nowinandroid.feature.author import androidx.lifecycle.SavedStateHandle -import app.cash.turbine.test import com.google.samples.apps.nowinandroid.core.model.data.Author import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor import com.google.samples.apps.nowinandroid.core.model.data.NewsResource @@ -25,9 +24,12 @@ import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Vid import com.google.samples.apps.nowinandroid.core.testing.repository.TestAuthorsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository -import com.google.samples.apps.nowinandroid.core.testing.util.TestDispatcherRule +import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.feature.author.navigation.AuthorDestination +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import kotlinx.datetime.Instant import org.junit.Assert.assertEquals @@ -36,10 +38,14 @@ import org.junit.Before import org.junit.Rule import org.junit.Test +/** + * To learn more about how this test handles Flows created with stateIn, see + * https://developer.android.com/kotlin/flow/test#statein + */ class AuthorViewModelTest { @get:Rule - val dispatcherRule = TestDispatcherRule() + val dispatcherRule = MainDispatcherRule() private val userDataRepository = TestUserDataRepository() private val authorsRepository = TestAuthorsRepository() @@ -62,90 +68,91 @@ class AuthorViewModelTest { @Test fun uiStateAuthor_whenSuccess_matchesAuthorFromRepository() = runTest { - viewModel.uiState.test { - awaitItem() - // To make sure AuthorUiState is success - authorsRepository.sendAuthors(testInputAuthors.map(FollowableAuthor::author)) - userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id)) + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } - val item = awaitItem() - assertTrue(item.authorState is AuthorUiState.Success) + // To make sure AuthorUiState is success + authorsRepository.sendAuthors(testInputAuthors.map(FollowableAuthor::author)) + userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id)) - val successAuthorUiState = item.authorState as AuthorUiState.Success - val authorFromRepository = authorsRepository.getAuthorStream( - id = testInputAuthors[0].author.id - ).first() + val item = viewModel.uiState.value + assertTrue(item.authorState is AuthorUiState.Success) - successAuthorUiState.followableAuthor.author - assertEquals(authorFromRepository, successAuthorUiState.followableAuthor.author) - } + val successAuthorUiState = item.authorState as AuthorUiState.Success + val authorFromRepository = authorsRepository.getAuthorStream( + id = testInputAuthors[0].author.id + ).first() + + successAuthorUiState.followableAuthor.author + assertEquals(authorFromRepository, successAuthorUiState.followableAuthor.author) + + collectJob.cancel() } @Test fun uiStateNews_whenInitialized_thenShowLoading() = runTest { - viewModel.uiState.test { - assertEquals(NewsUiState.Loading, awaitItem().newsState) - } + assertEquals(NewsUiState.Loading, viewModel.uiState.value.newsState) } @Test fun uiStateAuthor_whenInitialized_thenShowLoading() = runTest { - viewModel.uiState.test { - assertEquals(AuthorUiState.Loading, awaitItem().authorState) - } + assertEquals(AuthorUiState.Loading, viewModel.uiState.value.authorState) } @Test fun uiStateAuthor_whenFollowedIdsSuccessAndAuthorLoading_thenShowLoading() = runTest { - viewModel.uiState.test { - userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id)) - assertEquals(AuthorUiState.Loading, awaitItem().authorState) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id)) + assertEquals(AuthorUiState.Loading, viewModel.uiState.value.authorState) + + collectJob.cancel() } @Test fun uiStateAuthor_whenFollowedIdsSuccessAndAuthorSuccess_thenAuthorSuccessAndNewsLoading() = runTest { - viewModel.uiState.test { - awaitItem() - authorsRepository.sendAuthors(testInputAuthors.map { it.author }) - userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id)) - val item = awaitItem() - assertTrue(item.authorState is AuthorUiState.Success) - assertTrue(item.newsState is NewsUiState.Loading) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + authorsRepository.sendAuthors(testInputAuthors.map { it.author }) + userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id)) + val item = viewModel.uiState.value + assertTrue(item.authorState is AuthorUiState.Success) + assertTrue(item.newsState is NewsUiState.Loading) + + collectJob.cancel() } @Test fun uiStateAuthor_whenFollowedIdsSuccessAndAuthorSuccessAndNewsIsSuccess_thenAllSuccess() = runTest { - viewModel.uiState.test { - awaitItem() - authorsRepository.sendAuthors(testInputAuthors.map { it.author }) - userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id)) - newsRepository.sendNewsResources(sampleNewsResources) - val item = awaitItem() - assertTrue(item.authorState is AuthorUiState.Success) - assertTrue(item.newsState is NewsUiState.Success) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + authorsRepository.sendAuthors(testInputAuthors.map { it.author }) + userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id)) + newsRepository.sendNewsResources(sampleNewsResources) + val item = viewModel.uiState.value + assertTrue(item.authorState is AuthorUiState.Success) + assertTrue(item.newsState is NewsUiState.Success) + + collectJob.cancel() } @Test fun uiStateAuthor_whenFollowingAuthor_thenShowUpdatedAuthor() = runTest { - viewModel.uiState - .test { - awaitItem() - authorsRepository.sendAuthors(testInputAuthors.map { it.author }) - // Set which author IDs are followed, not including 0. - userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id)) - - viewModel.followAuthorToggle(true) - - assertEquals( - AuthorUiState.Success(followableAuthor = testOutputAuthors[0]), - awaitItem().authorState - ) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + authorsRepository.sendAuthors(testInputAuthors.map { it.author }) + // Set which author IDs are followed, not including 0. + userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id)) + + viewModel.followAuthorToggle(true) + + assertEquals( + AuthorUiState.Success(followableAuthor = testOutputAuthors[0]), + viewModel.uiState.value.authorState + ) + + collectJob.cancel() } } diff --git a/feature-foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt b/feature-foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt index dba50dccb..ca4ef7094 100644 --- a/feature-foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt +++ b/feature-foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt @@ -17,7 +17,6 @@ package com.google.samples.apps.nowinandroid.feature.foryou import androidx.lifecycle.SavedStateHandle -import app.cash.turbine.test import com.google.samples.apps.nowinandroid.core.model.data.Author import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic @@ -29,8 +28,10 @@ import com.google.samples.apps.nowinandroid.core.testing.repository.TestAuthorsR import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository -import com.google.samples.apps.nowinandroid.core.testing.util.TestDispatcherRule -import kotlinx.coroutines.flow.combine +import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import kotlinx.datetime.Instant @@ -39,9 +40,13 @@ import org.junit.Before import org.junit.Rule import org.junit.Test +/** + * To learn more about how this test handles Flows created with stateIn, see + * https://developer.android.com/kotlin/flow/test#statein + */ class ForYouViewModelTest { @get:Rule - val dispatcherRule = TestDispatcherRule() + val mainDispatcherRule = MainDispatcherRule() private val userDataRepository = TestUserDataRepository() private val authorsRepository = TestAuthorsRepository() @@ -60,1449 +65,1308 @@ class ForYouViewModelTest { ) } - /** - * A pairing of [ForYouInterestsSelectionUiState] and [ForYouFeedUiState] for ease of testing - * state updates as a single flow. - */ - private data class ForYouUiState( - val interestsSelectionState: ForYouInterestsSelectionUiState, - val feedState: ForYouFeedUiState, - ) - - private val ForYouViewModel.uiState - get() = - combine( - interestsSelectionState, - feedState, - ::ForYouUiState - ) - @Test fun stateIsInitiallyLoading() = runTest { - viewModel.uiState.test { - assertEquals( - ForYouUiState( - ForYouInterestsSelectionUiState.Loading, - ForYouFeedUiState.Loading - ), - awaitItem() - ) - } + assertEquals( + ForYouInterestsSelectionUiState.Loading, + viewModel.interestsSelectionState.value + ) + assertEquals(ForYouFeedUiState.Loading, viewModel.feedState.value) } @Test fun stateIsLoadingWhenFollowedTopicsAreLoading() = runTest { - viewModel.uiState.test { - assertEquals( - ForYouUiState( - ForYouInterestsSelectionUiState.Loading, - ForYouFeedUiState.Loading - ), - awaitItem() - ) - topicsRepository.sendTopics(sampleTopics) - } + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + + assertEquals( + ForYouInterestsSelectionUiState.Loading, + viewModel.interestsSelectionState.value + ) + assertEquals(ForYouFeedUiState.Loading, viewModel.feedState.value) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun stateIsLoadingWhenFollowedAuthorsAreLoading() = runTest { - viewModel.uiState.test { - assertEquals( - ForYouUiState( - ForYouInterestsSelectionUiState.Loading, - ForYouFeedUiState.Loading - ), - awaitItem() - ) - authorsRepository.sendAuthors(sampleAuthors) - } + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + authorsRepository.sendAuthors(sampleAuthors) + + assertEquals( + ForYouInterestsSelectionUiState.Loading, + viewModel.interestsSelectionState.value + ) + assertEquals(ForYouFeedUiState.Loading, viewModel.feedState.value) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun stateIsLoadingWhenTopicsAreLoading() = runTest { - viewModel.uiState.test { - assertEquals( - ForYouUiState( - ForYouInterestsSelectionUiState.Loading, - ForYouFeedUiState.Loading - ), - awaitItem() - ) - userDataRepository.setFollowedTopicIds(emptySet()) - } + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + userDataRepository.setFollowedTopicIds(emptySet()) + + assertEquals( + ForYouInterestsSelectionUiState.Loading, + viewModel.interestsSelectionState.value + ) + assertEquals(ForYouFeedUiState.Success(emptyList()), viewModel.feedState.value) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun stateIsLoadingWhenAuthorsAreLoading() = runTest { - viewModel.uiState.test { - assertEquals( - ForYouUiState( - ForYouInterestsSelectionUiState.Loading, - ForYouFeedUiState.Loading - ), - awaitItem() - ) - userDataRepository.setFollowedAuthorIds(emptySet()) - } + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + userDataRepository.setFollowedAuthorIds(emptySet()) + + assertEquals( + ForYouInterestsSelectionUiState.Loading, + viewModel.interestsSelectionState.value + ) + assertEquals(ForYouFeedUiState.Success(emptyList()), viewModel.feedState.value) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun stateIsInterestsSelectionWhenNewsResourcesAreLoading() = runTest { - viewModel.uiState.test { - advanceUntilIdle() - expectMostRecentItem() - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(emptySet()) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(emptySet()) - - advanceUntilIdle() - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(emptySet()) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(emptySet()) + + assertEquals( + ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = listOf( + FollowableTopic( + topic = Topic( + id = "0", + name = "Headlines", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ), + FollowableTopic( + topic = Topic( + id = "1", + name = "UI", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) + isFollowed = false + ), + FollowableTopic( + topic = Topic( + id = "2", + name = "Tools", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", ), + isFollowed = false ), - feedState = ForYouFeedUiState.Success( - feed = emptyList() + ), + authors = listOf( + FollowableAuthor( + author = Author( + id = "0", + name = "Android Dev", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "1", + name = "Android Dev 2", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "2", + name = "Android Dev 3", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false ) ), - expectMostRecentItem() - ) - } + ), + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = emptyList() + ), + viewModel.feedState.value + ) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun stateIsInterestsSelectionAfterLoadingEmptyFollowedTopicsAndAuthors() = runTest { - viewModel.uiState - .test { - topicsRepository.sendTopics(sampleTopics) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedTopicIds(emptySet()) - userDataRepository.setFollowedAuthorIds(emptySet()) - newsRepository.sendNewsResources(sampleNewsResources) - - advanceUntilIdle() - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) - ), + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedTopicIds(emptySet()) + userDataRepository.setFollowedAuthorIds(emptySet()) + newsRepository.sendNewsResources(sampleNewsResources) + + assertEquals( + ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = listOf( + FollowableTopic( + topic = Topic( + id = "0", + name = "Headlines", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", ), - feedState = ForYouFeedUiState.Success( - feed = emptyList() - ) + isFollowed = false ), - expectMostRecentItem() - ) - } + FollowableTopic( + topic = Topic( + id = "1", + name = "UI", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ), + FollowableTopic( + topic = Topic( + id = "2", + name = "Tools", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ), + ), + authors = listOf( + FollowableAuthor( + author = Author( + id = "0", + name = "Android Dev", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "1", + name = "Android Dev 2", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "2", + name = "Android Dev 3", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ) + ), + ), + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = emptyList() + + ), + viewModel.feedState.value + ) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun stateIsWithoutInterestsSelectionAfterLoadingFollowedTopics() = runTest { - viewModel.uiState - .test { - advanceUntilIdle() - expectMostRecentItem() - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(emptySet()) - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(setOf("0", "1")) - - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.NoInterestsSelection, - feedState = ForYouFeedUiState.Loading - ), - awaitItem() - ) + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } - newsRepository.sendNewsResources(sampleNewsResources) - - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.NoInterestsSelection, - feedState = ForYouFeedUiState.Success( - feed = sampleNewsResources.map { - SaveableNewsResource( - newsResource = it, - isSaved = false - ) - } - ) - ), - awaitItem() - ) - } + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(emptySet()) + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(setOf("0", "1")) + + assertEquals( + ForYouInterestsSelectionUiState.NoInterestsSelection, + viewModel.interestsSelectionState.value + ) + assertEquals(ForYouFeedUiState.Loading, viewModel.feedState.value) + + newsRepository.sendNewsResources(sampleNewsResources) + + assertEquals( + ForYouInterestsSelectionUiState.NoInterestsSelection, + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = + sampleNewsResources.map { + SaveableNewsResource( + newsResource = it, + isSaved = false + ) + } + ), + viewModel.feedState.value + ) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun stateIsWithoutInterestsSelectionAfterLoadingFollowedAuthors() = runTest { - viewModel.uiState - .test { - advanceUntilIdle() - expectMostRecentItem() - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(setOf("0", "1")) - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(emptySet()) - - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.NoInterestsSelection, - feedState = ForYouFeedUiState.Loading - ), - awaitItem() - ) + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } - newsRepository.sendNewsResources(sampleNewsResources) - - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.NoInterestsSelection, - feedState = ForYouFeedUiState.Success( - feed = sampleNewsResources.map { - SaveableNewsResource( - newsResource = it, - isSaved = false - ) - } - ) - ), - awaitItem() - ) - } + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(setOf("0", "1")) + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(emptySet()) + + assertEquals( + ForYouInterestsSelectionUiState.NoInterestsSelection, + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Loading, + viewModel.feedState.value + ) + + newsRepository.sendNewsResources(sampleNewsResources) + + assertEquals( + ForYouInterestsSelectionUiState.NoInterestsSelection, + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = sampleNewsResources.map { + SaveableNewsResource( + newsResource = it, + isSaved = false + ) + } + ), + viewModel.feedState.value + ) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun topicSelectionUpdatesAfterSelectingTopic() = runTest { - viewModel.uiState - .test { - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(emptySet()) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(emptySet()) - newsRepository.sendNewsResources(sampleNewsResources) - - advanceUntilIdle() - expectMostRecentItem() - - viewModel.updateTopicSelection("1", isChecked = true) - - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = true - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ) - ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) - ), + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(emptySet()) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(emptySet()) + newsRepository.sendNewsResources(sampleNewsResources) + + assertEquals( + ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = listOf( + FollowableTopic( + topic = Topic( + id = "0", + name = "Headlines", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", ), - feedState = ForYouFeedUiState.Success( - feed = emptyList(), - ) + isFollowed = false ), - awaitItem() - ) - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = true - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ) - ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) - ), + FollowableTopic( + topic = Topic( + id = "1", + name = "UI", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", ), - feedState = ForYouFeedUiState.Loading + isFollowed = false ), - awaitItem() - ) - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = true - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ) - ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) - ), + FollowableTopic( + topic = Topic( + id = "2", + name = "Tools", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ) + ), + authors = listOf( + FollowableAuthor( + author = Author( + id = "0", + name = "Android Dev", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", ), - feedState = ForYouFeedUiState.Success( - feed = listOf( - SaveableNewsResource( - newsResource = sampleNewsResources[1], - isSaved = false - ), - SaveableNewsResource( - newsResource = sampleNewsResources[2], - isSaved = false - ) - ) - ) + isFollowed = false ), - awaitItem() + FollowableAuthor( + author = Author( + id = "1", + name = "Android Dev 2", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "2", + name = "Android Dev 3", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ) + ), + ), + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = emptyList(), + ), + viewModel.feedState.value + ) + + viewModel.updateTopicSelection("1", isChecked = true) + + assertEquals( + ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = listOf( + FollowableTopic( + topic = Topic( + id = "0", + name = "Headlines", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ), + FollowableTopic( + topic = Topic( + id = "1", + name = "UI", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = true + ), + FollowableTopic( + topic = Topic( + id = "2", + name = "Tools", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ) + ), + authors = listOf( + FollowableAuthor( + author = Author( + id = "0", + name = "Android Dev", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "1", + name = "Android Dev 2", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "2", + name = "Android Dev 3", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ) + ), + ), + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = listOf( + SaveableNewsResource( + newsResource = sampleNewsResources[1], + isSaved = false + ), + SaveableNewsResource( + newsResource = sampleNewsResources[2], + isSaved = false + ) ) - } + ), + viewModel.feedState.value + ) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun topicSelectionUpdatesAfterSelectingAuthor() = runTest { - viewModel.uiState - .test { - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(emptySet()) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(emptySet()) - newsRepository.sendNewsResources(sampleNewsResources) - - advanceUntilIdle() - expectMostRecentItem() - - viewModel.updateAuthorSelection("1", isChecked = true) - - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ) - ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = true - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) - ), + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(emptySet()) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(emptySet()) + newsRepository.sendNewsResources(sampleNewsResources) + + assertEquals( + ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = listOf( + FollowableTopic( + topic = Topic( + id = "0", + name = "Headlines", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", ), - feedState = ForYouFeedUiState.Success( - feed = emptyList(), - ) + isFollowed = false ), - awaitItem() - ) - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ) - ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = true - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) - ), + FollowableTopic( + topic = Topic( + id = "1", + name = "UI", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", ), - feedState = ForYouFeedUiState.Loading + isFollowed = false ), - awaitItem() - ) - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ) - ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = true - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) - ), + FollowableTopic( + topic = Topic( + id = "2", + name = "Tools", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ) + ), + authors = listOf( + FollowableAuthor( + author = Author( + id = "0", + name = "Android Dev", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", ), - feedState = ForYouFeedUiState.Success( - feed = listOf( - SaveableNewsResource( - newsResource = sampleNewsResources[1], - isSaved = false - ), - SaveableNewsResource( - newsResource = sampleNewsResources[2], - isSaved = false - ) - ) - ) + isFollowed = false ), - awaitItem() + FollowableAuthor( + author = Author( + id = "1", + name = "Android Dev 2", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "2", + name = "Android Dev 3", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ) + ), + ), + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = emptyList(), + ), + viewModel.feedState.value + ) + + viewModel.updateAuthorSelection("1", isChecked = true) + + assertEquals( + ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = listOf( + FollowableTopic( + topic = Topic( + id = "0", + name = "Headlines", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ), + FollowableTopic( + topic = Topic( + id = "1", + name = "UI", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ), + FollowableTopic( + topic = Topic( + id = "2", + name = "Tools", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ) + ), + authors = listOf( + FollowableAuthor( + author = Author( + id = "0", + name = "Android Dev", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "1", + name = "Android Dev 2", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = true + ), + FollowableAuthor( + author = Author( + id = "2", + name = "Android Dev 3", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ) + ), + ), + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = listOf( + SaveableNewsResource( + newsResource = sampleNewsResources[1], + isSaved = false + ), + SaveableNewsResource( + newsResource = sampleNewsResources[2], + isSaved = false + ) ) - } + ), + viewModel.feedState.value + ) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun topicSelectionUpdatesAfterUnselectingTopic() = runTest { - viewModel.uiState - .test { - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(emptySet()) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(emptySet()) - newsRepository.sendNewsResources(sampleNewsResources) - viewModel.updateTopicSelection("1", isChecked = true) - viewModel.updateTopicSelection("1", isChecked = false) - - advanceUntilIdle() - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ) - ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) - ), + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(emptySet()) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(emptySet()) + newsRepository.sendNewsResources(sampleNewsResources) + viewModel.updateTopicSelection("1", isChecked = true) + viewModel.updateTopicSelection("1", isChecked = false) + + advanceUntilIdle() + assertEquals( + ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = listOf( + FollowableTopic( + topic = Topic( + id = "0", + name = "Headlines", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", ), - feedState = ForYouFeedUiState.Success( - feed = emptyList() - ) + isFollowed = false ), - expectMostRecentItem() - ) - } + FollowableTopic( + topic = Topic( + id = "1", + name = "UI", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ), + FollowableTopic( + topic = Topic( + id = "2", + name = "Tools", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ) + ), + authors = listOf( + FollowableAuthor( + author = Author( + id = "0", + name = "Android Dev", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "1", + name = "Android Dev 2", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "2", + name = "Android Dev 3", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ) + ), + ), + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = emptyList() + ), + viewModel.feedState.value + ) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun topicSelectionUpdatesAfterUnselectingAuthor() = runTest { - viewModel.uiState - .test { - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(emptySet()) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(emptySet()) - newsRepository.sendNewsResources(sampleNewsResources) - viewModel.updateAuthorSelection("1", isChecked = true) - viewModel.updateAuthorSelection("1", isChecked = false) - - advanceUntilIdle() - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ) - ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) - ), + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(emptySet()) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(emptySet()) + newsRepository.sendNewsResources(sampleNewsResources) + viewModel.updateAuthorSelection("1", isChecked = true) + viewModel.updateAuthorSelection("1", isChecked = false) + + assertEquals( + + ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = listOf( + FollowableTopic( + topic = Topic( + id = "0", + name = "Headlines", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", ), - feedState = ForYouFeedUiState.Success( - feed = emptyList() - ) + isFollowed = false ), - expectMostRecentItem() - ) - } + FollowableTopic( + topic = Topic( + id = "1", + name = "UI", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ), + FollowableTopic( + topic = Topic( + id = "2", + name = "Tools", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ) + ), + authors = listOf( + FollowableAuthor( + author = Author( + id = "0", + name = "Android Dev", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "1", + name = "Android Dev 2", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "2", + name = "Android Dev 3", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ) + ), + ), + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = emptyList() + ), + viewModel.feedState.value + ) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun topicSelectionUpdatesAfterSavingTopicsOnly() = runTest { - viewModel.uiState - .test { - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(emptySet()) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(emptySet()) - newsRepository.sendNewsResources(sampleNewsResources) - viewModel.updateTopicSelection("1", isChecked = true) - - advanceUntilIdle() - expectMostRecentItem() - - viewModel.saveFollowedInterests() - - advanceUntilIdle() - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.NoInterestsSelection, - feedState = ForYouFeedUiState.Success( - feed = listOf( - SaveableNewsResource( - newsResource = sampleNewsResources[1], - isSaved = false, - ), - SaveableNewsResource( - newsResource = sampleNewsResources[2], - isSaved = false, - ) - ) - ) + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(emptySet()) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(emptySet()) + newsRepository.sendNewsResources(sampleNewsResources) + viewModel.updateTopicSelection("1", isChecked = true) + + viewModel.saveFollowedInterests() + + assertEquals( + ForYouInterestsSelectionUiState.NoInterestsSelection, + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = listOf( + SaveableNewsResource( + newsResource = sampleNewsResources[1], + isSaved = false, ), - expectMostRecentItem() + SaveableNewsResource( + newsResource = sampleNewsResources[2], + isSaved = false, + ) ) - assertEquals(setOf("1"), userDataRepository.getCurrentFollowedTopics()) - assertEquals(emptySet(), userDataRepository.getCurrentFollowedAuthors()) - } + ), + viewModel.feedState.value + ) + assertEquals(setOf("1"), userDataRepository.getCurrentFollowedTopics()) + assertEquals(emptySet(), userDataRepository.getCurrentFollowedAuthors()) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun topicSelectionUpdatesAfterSavingAuthorsOnly() = runTest { - viewModel.uiState - .test { - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(emptySet()) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(emptySet()) - newsRepository.sendNewsResources(sampleNewsResources) - viewModel.updateAuthorSelection("0", isChecked = true) - - advanceUntilIdle() - expectMostRecentItem() - - viewModel.saveFollowedInterests() - - advanceUntilIdle() - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.NoInterestsSelection, - feedState = ForYouFeedUiState.Success( - feed = listOf( - SaveableNewsResource( - newsResource = sampleNewsResources[0], - isSaved = false - ), - ) - ) + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(emptySet()) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(emptySet()) + newsRepository.sendNewsResources(sampleNewsResources) + viewModel.updateAuthorSelection("0", isChecked = true) + + viewModel.saveFollowedInterests() + + assertEquals( + ForYouInterestsSelectionUiState.NoInterestsSelection, + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = listOf( + SaveableNewsResource( + newsResource = sampleNewsResources[0], + isSaved = false ), - expectMostRecentItem() ) - assertEquals(emptySet(), userDataRepository.getCurrentFollowedTopics()) - assertEquals(setOf("0"), userDataRepository.getCurrentFollowedAuthors()) - } + ), + viewModel.feedState.value + ) + assertEquals(emptySet(), userDataRepository.getCurrentFollowedTopics()) + assertEquals(setOf("0"), userDataRepository.getCurrentFollowedAuthors()) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun topicSelectionUpdatesAfterSavingAuthorsAndTopics() = runTest { - viewModel.uiState - .test { - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(emptySet()) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(emptySet()) - newsRepository.sendNewsResources(sampleNewsResources) - viewModel.updateAuthorSelection("1", isChecked = true) - viewModel.updateTopicSelection("1", isChecked = true) - - advanceUntilIdle() - expectMostRecentItem() - - viewModel.saveFollowedInterests() - - advanceUntilIdle() - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.NoInterestsSelection, - feedState = ForYouFeedUiState.Success( - feed = listOf( - SaveableNewsResource( - newsResource = sampleNewsResources[1], - isSaved = false - ), - SaveableNewsResource( - newsResource = sampleNewsResources[2], - isSaved = false - ) - ) - ) + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(emptySet()) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(emptySet()) + newsRepository.sendNewsResources(sampleNewsResources) + viewModel.updateAuthorSelection("1", isChecked = true) + viewModel.updateTopicSelection("1", isChecked = true) + + viewModel.saveFollowedInterests() + + assertEquals( + ForYouInterestsSelectionUiState.NoInterestsSelection, + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = listOf( + SaveableNewsResource( + newsResource = sampleNewsResources[1], + isSaved = false ), - expectMostRecentItem() + SaveableNewsResource( + newsResource = sampleNewsResources[2], + isSaved = false + ) ) - assertEquals(setOf("1"), userDataRepository.getCurrentFollowedTopics()) - assertEquals(setOf("1"), userDataRepository.getCurrentFollowedAuthors()) - } + ), + viewModel.feedState.value + ) + assertEquals(setOf("1"), userDataRepository.getCurrentFollowedTopics()) + assertEquals(setOf("1"), userDataRepository.getCurrentFollowedAuthors()) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun topicSelectionIsResetAfterSavingTopicsAndRemovingThem() = runTest { - viewModel.uiState - .test { - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(emptySet()) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(emptySet()) - newsRepository.sendNewsResources(sampleNewsResources) - viewModel.updateTopicSelection("1", isChecked = true) - viewModel.saveFollowedInterests() - - advanceUntilIdle() - expectMostRecentItem() - - userDataRepository.setFollowedTopicIds(emptySet()) - - advanceUntilIdle() - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ) - ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) - ) + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(emptySet()) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(emptySet()) + newsRepository.sendNewsResources(sampleNewsResources) + viewModel.updateTopicSelection("1", isChecked = true) + viewModel.saveFollowedInterests() + + userDataRepository.setFollowedTopicIds(emptySet()) + + assertEquals( + ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = listOf( + FollowableTopic( + topic = Topic( + id = "0", + name = "Headlines", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", ), - feedState = ForYouFeedUiState.Success( - feed = emptyList() - ) + isFollowed = false ), - - expectMostRecentItem() + FollowableTopic( + topic = Topic( + id = "1", + name = "UI", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ), + FollowableTopic( + topic = Topic( + id = "2", + name = "Tools", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ) + ), + authors = listOf( + FollowableAuthor( + author = Author( + id = "0", + name = "Android Dev", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "1", + name = "Android Dev 2", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "2", + name = "Android Dev 3", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ) ) - } + ), + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = emptyList() + ), + viewModel.feedState.value + ) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun authorSelectionIsResetAfterSavingAuthorsAndRemovingThem() = runTest { - viewModel.uiState - .test { - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(emptySet()) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(emptySet()) - newsRepository.sendNewsResources(sampleNewsResources) - viewModel.updateAuthorSelection("1", isChecked = true) - viewModel.saveFollowedInterests() - - advanceUntilIdle() - expectMostRecentItem() - - userDataRepository.setFollowedAuthorIds(emptySet()) - - advanceUntilIdle() - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = listOf( - FollowableTopic( - topic = Topic( - id = "0", - name = "Headlines", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "1", - name = "UI", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ), - FollowableTopic( - topic = Topic( - id = "2", - name = "Tools", - shortDescription = "", - longDescription = "long description", - url = "URL", - imageUrl = "image URL", - ), - isFollowed = false - ) - ), - authors = listOf( - FollowableAuthor( - author = Author( - id = "0", - name = "Android Dev", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "1", - name = "Android Dev 2", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ), - FollowableAuthor( - author = Author( - id = "2", - name = "Android Dev 3", - imageUrl = "", - twitter = "", - mediumPage = "", - bio = "", - ), - isFollowed = false - ) - ) + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(emptySet()) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(emptySet()) + newsRepository.sendNewsResources(sampleNewsResources) + viewModel.updateAuthorSelection("1", isChecked = true) + viewModel.saveFollowedInterests() + + userDataRepository.setFollowedAuthorIds(emptySet()) + + assertEquals( + ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = listOf( + FollowableTopic( + topic = Topic( + id = "0", + name = "Headlines", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ), + FollowableTopic( + topic = Topic( + id = "1", + name = "UI", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", ), - feedState = ForYouFeedUiState.Success( - feed = emptyList() - ) + isFollowed = false ), - expectMostRecentItem() + FollowableTopic( + topic = Topic( + id = "2", + name = "Tools", + shortDescription = "", + longDescription = "long description", + url = "URL", + imageUrl = "image URL", + ), + isFollowed = false + ) + ), + authors = listOf( + FollowableAuthor( + author = Author( + id = "0", + name = "Android Dev", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "1", + name = "Android Dev 2", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ), + FollowableAuthor( + author = Author( + id = "2", + name = "Android Dev 3", + imageUrl = "", + twitter = "", + mediumPage = "", + bio = "", + ), + isFollowed = false + ) ) - } + ), + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = emptyList() + ), + viewModel.feedState.value + ) + + collectJob1.cancel() + collectJob2.cancel() } @Test fun newsResourceSelectionUpdatesAfterLoadingFollowedTopics() = runTest { - viewModel.uiState - .test { - topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(setOf("1")) - authorsRepository.sendAuthors(sampleAuthors) - userDataRepository.setFollowedAuthorIds(setOf("1")) - newsRepository.sendNewsResources(sampleNewsResources) - viewModel.updateNewsResourceSaved("2", true) - - advanceUntilIdle() - assertEquals( - ForYouUiState( - interestsSelectionState = - ForYouInterestsSelectionUiState.NoInterestsSelection, - feedState = ForYouFeedUiState.Success( - feed = listOf( - SaveableNewsResource( - newsResource = sampleNewsResources[1], - isSaved = true - ), - SaveableNewsResource( - newsResource = sampleNewsResources[2], - isSaved = false - ) - ) - ) + val collectJob1 = + launch(UnconfinedTestDispatcher()) { viewModel.interestsSelectionState.collect() } + val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } + + topicsRepository.sendTopics(sampleTopics) + userDataRepository.setFollowedTopicIds(setOf("1")) + authorsRepository.sendAuthors(sampleAuthors) + userDataRepository.setFollowedAuthorIds(setOf("1")) + newsRepository.sendNewsResources(sampleNewsResources) + viewModel.updateNewsResourceSaved("2", true) + + assertEquals( + ForYouInterestsSelectionUiState.NoInterestsSelection, + viewModel.interestsSelectionState.value + ) + assertEquals( + ForYouFeedUiState.Success( + feed = listOf( + SaveableNewsResource( + newsResource = sampleNewsResources[1], + isSaved = true ), - expectMostRecentItem() + SaveableNewsResource( + newsResource = sampleNewsResources[2], + isSaved = false + ) ) - } + ), + viewModel.feedState.value + ) + + collectJob1.cancel() + collectJob2.cancel() } } diff --git a/feature-interests/src/test/java/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt b/feature-interests/src/test/java/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt index a341f7fd6..c36625f43 100644 --- a/feature-interests/src/test/java/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt +++ b/feature-interests/src/test/java/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt @@ -16,7 +16,6 @@ package com.google.samples.apps.nowinandroid.interests -import app.cash.turbine.test import com.google.samples.apps.nowinandroid.core.model.data.Author import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic @@ -24,19 +23,26 @@ import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.testing.repository.TestAuthorsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository -import com.google.samples.apps.nowinandroid.core.testing.util.TestDispatcherRule +import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.feature.interests.InterestsUiState import com.google.samples.apps.nowinandroid.feature.interests.InterestsViewModel +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test +/** + * To learn more about how this test handles Flows created with stateIn, see + * https://developer.android.com/kotlin/flow/test#statein + */ class InterestsViewModelTest { @get:Rule - val dispatcherRule = TestDispatcherRule() + val mainDispatcherRule = MainDispatcherRule() private val userDataRepository = TestUserDataRepository() private val authorsRepository = TestAuthorsRepository() @@ -54,135 +60,136 @@ class InterestsViewModelTest { @Test fun uiState_whenInitialized_thenShowLoading() = runTest { - viewModel.uiState.test { - assertEquals(InterestsUiState.Loading, awaitItem()) - } + assertEquals(InterestsUiState.Loading, viewModel.uiState.value) } @Test fun uiState_whenFollowedTopicsAreLoading_thenShowLoading() = runTest { - viewModel.uiState.test { - assertEquals(InterestsUiState.Loading, awaitItem()) - userDataRepository.setFollowedAuthorIds(setOf("1")) - userDataRepository.setFollowedTopicIds(emptySet()) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + userDataRepository.setFollowedAuthorIds(setOf("1")) + userDataRepository.setFollowedTopicIds(emptySet()) + assertEquals(InterestsUiState.Loading, viewModel.uiState.value) + + collectJob.cancel() } @Test fun uiState_whenFollowedAuthorsAreLoading_thenShowLoading() = runTest { - viewModel.uiState.test { - assertEquals(InterestsUiState.Loading, awaitItem()) - userDataRepository.setFollowedAuthorIds(emptySet()) - userDataRepository.setFollowedTopicIds(setOf("1")) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + userDataRepository.setFollowedAuthorIds(emptySet()) + userDataRepository.setFollowedTopicIds(setOf("1")) + assertEquals(InterestsUiState.Loading, viewModel.uiState.value) + + collectJob.cancel() } @Test fun uiState_whenFollowingNewTopic_thenShowUpdatedTopics() = runTest { + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + val toggleTopicId = testOutputTopics[1].topic.id - viewModel.uiState - .test { - awaitItem() - authorsRepository.sendAuthors(emptyList()) - userDataRepository.setFollowedAuthorIds(emptySet()) - topicsRepository.sendTopics(testInputTopics.map { it.topic }) - userDataRepository.setFollowedTopicIds(setOf(testInputTopics[0].topic.id)) - - assertEquals( - false, - (awaitItem() as InterestsUiState.Interests) - .topics.first { it.topic.id == toggleTopicId }.isFollowed - ) - - viewModel.followTopic( - followedTopicId = toggleTopicId, - true - ) - - assertEquals( - InterestsUiState.Interests(topics = testOutputTopics, authors = emptyList()), - awaitItem() - ) - } + authorsRepository.sendAuthors(emptyList()) + userDataRepository.setFollowedAuthorIds(emptySet()) + topicsRepository.sendTopics(testInputTopics.map { it.topic }) + userDataRepository.setFollowedTopicIds(setOf(testInputTopics[0].topic.id)) + + assertEquals( + false, + (viewModel.uiState.value as InterestsUiState.Interests) + .topics.first { it.topic.id == toggleTopicId }.isFollowed + ) + + viewModel.followTopic( + followedTopicId = toggleTopicId, + true + ) + + assertEquals( + InterestsUiState.Interests(topics = testOutputTopics, authors = emptyList()), + viewModel.uiState.value + ) + + collectJob.cancel() } @Test fun uiState_whenFollowingNewAuthor_thenShowUpdatedAuthors() = runTest { - viewModel.uiState - .test { - awaitItem() - authorsRepository.sendAuthors(testInputAuthors.map { it.author }) - userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[0].author.id)) - topicsRepository.sendTopics(listOf()) - userDataRepository.setFollowedTopicIds(setOf()) - - awaitItem() - viewModel.followAuthor( - followedAuthorId = testInputAuthors[1].author.id, - followed = true - ) - - assertEquals( - InterestsUiState.Interests(topics = emptyList(), authors = testOutputAuthors), - awaitItem() - ) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + authorsRepository.sendAuthors(testInputAuthors.map { it.author }) + userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[0].author.id)) + topicsRepository.sendTopics(listOf()) + userDataRepository.setFollowedTopicIds(setOf()) + + viewModel.followAuthor( + followedAuthorId = testInputAuthors[1].author.id, + followed = true + ) + + assertEquals( + InterestsUiState.Interests(topics = emptyList(), authors = testOutputAuthors), + viewModel.uiState.value + ) + + collectJob.cancel() } @Test fun uiState_whenUnfollowingTopics_thenShowUpdatedTopics() = runTest { + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + val toggleTopicId = testOutputTopics[1].topic.id - viewModel.uiState - .test { - awaitItem() - authorsRepository.sendAuthors(emptyList()) - userDataRepository.setFollowedAuthorIds(emptySet()) - topicsRepository.sendTopics(testOutputTopics.map { it.topic }) - userDataRepository.setFollowedTopicIds( - setOf(testOutputTopics[0].topic.id, testOutputTopics[1].topic.id) - ) - - assertEquals( - true, - (awaitItem() as InterestsUiState.Interests) - .topics.first { it.topic.id == toggleTopicId }.isFollowed - ) - - viewModel.followTopic( - followedTopicId = toggleTopicId, - false - ) - - assertEquals( - InterestsUiState.Interests(topics = testInputTopics, authors = emptyList()), - awaitItem() - ) - } + + authorsRepository.sendAuthors(emptyList()) + userDataRepository.setFollowedAuthorIds(emptySet()) + topicsRepository.sendTopics(testOutputTopics.map { it.topic }) + userDataRepository.setFollowedTopicIds( + setOf(testOutputTopics[0].topic.id, testOutputTopics[1].topic.id) + ) + + assertEquals( + true, + (viewModel.uiState.value as InterestsUiState.Interests) + .topics.first { it.topic.id == toggleTopicId }.isFollowed + ) + + viewModel.followTopic( + followedTopicId = toggleTopicId, + false + ) + + assertEquals( + InterestsUiState.Interests(topics = testInputTopics, authors = emptyList()), + viewModel.uiState.value + ) + + collectJob.cancel() } @Test fun uiState_whenUnfollowingAuthors_thenShowUpdatedAuthors() = runTest { - viewModel.uiState - .test { - awaitItem() - authorsRepository.sendAuthors(testOutputAuthors.map { it.author }) - userDataRepository.setFollowedAuthorIds( - setOf(testOutputAuthors[0].author.id, testOutputAuthors[1].author.id) - ) - topicsRepository.sendTopics(listOf()) - userDataRepository.setFollowedTopicIds(setOf()) - - awaitItem() - viewModel.followAuthor( - followedAuthorId = testOutputAuthors[1].author.id, - followed = false - ) - - assertEquals( - InterestsUiState.Interests(topics = emptyList(), authors = testInputAuthors), - awaitItem() - ) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + authorsRepository.sendAuthors(testOutputAuthors.map { it.author }) + userDataRepository.setFollowedAuthorIds( + setOf(testOutputAuthors[0].author.id, testOutputAuthors[1].author.id) + ) + topicsRepository.sendTopics(listOf()) + userDataRepository.setFollowedTopicIds(setOf()) + + viewModel.followAuthor( + followedAuthorId = testOutputAuthors[1].author.id, + followed = false + ) + + assertEquals( + InterestsUiState.Interests(topics = emptyList(), authors = testInputAuthors), + viewModel.uiState.value + ) + + collectJob.cancel() } } diff --git a/feature-topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt b/feature-topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt index bbc183003..e29b33054 100644 --- a/feature-topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt +++ b/feature-topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt @@ -17,7 +17,6 @@ package com.google.samples.apps.nowinandroid.feature.topic import androidx.lifecycle.SavedStateHandle -import app.cash.turbine.test import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Video @@ -25,9 +24,12 @@ import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository -import com.google.samples.apps.nowinandroid.core.testing.util.TestDispatcherRule +import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicDestination +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import kotlinx.datetime.Instant import org.junit.Assert.assertEquals @@ -36,10 +38,14 @@ import org.junit.Before import org.junit.Rule import org.junit.Test +/** + * To learn more about how this test handles Flows created with stateIn, see + * https://developer.android.com/kotlin/flow/test#statein + */ class TopicViewModelTest { @get:Rule - val dispatcherRule = TestDispatcherRule() + val dispatcherRule = MainDispatcherRule() private val userDataRepository = TestUserDataRepository() private val topicsRepository = TestTopicsRepository() @@ -59,87 +65,88 @@ class TopicViewModelTest { @Test fun uiStateAuthor_whenSuccess_matchesTopicFromRepository() = runTest { - viewModel.uiState.test { - awaitItem() - topicsRepository.sendTopics(testInputTopics.map(FollowableTopic::topic)) - userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id)) - val item = awaitItem() - assertTrue(item.topicState is TopicUiState.Success) + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } - val successTopicState = item.topicState as TopicUiState.Success - val topicFromRepository = topicsRepository.getTopic( - testInputTopics[0].topic.id - ).first() + topicsRepository.sendTopics(testInputTopics.map(FollowableTopic::topic)) + userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id)) + val item = viewModel.uiState.value + assertTrue(item.topicState is TopicUiState.Success) - assertEquals(topicFromRepository, successTopicState.followableTopic.topic) - } + val successTopicState = item.topicState as TopicUiState.Success + val topicFromRepository = topicsRepository.getTopic( + testInputTopics[0].topic.id + ).first() + + assertEquals(topicFromRepository, successTopicState.followableTopic.topic) + + collectJob.cancel() } @Test fun uiStateNews_whenInitialized_thenShowLoading() = runTest { - viewModel.uiState.test { - assertEquals(NewsUiState.Loading, awaitItem().newsState) - } + assertEquals(NewsUiState.Loading, viewModel.uiState.value.newsState) } @Test fun uiStateTopic_whenInitialized_thenShowLoading() = runTest { - viewModel.uiState.test { - assertEquals(TopicUiState.Loading, awaitItem().topicState) - } + assertEquals(TopicUiState.Loading, viewModel.uiState.value.topicState) } @Test fun uiStateTopic_whenFollowedIdsSuccessAndTopicLoading_thenShowLoading() = runTest { - viewModel.uiState.test { - userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id)) - assertEquals(TopicUiState.Loading, awaitItem().topicState) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id)) + assertEquals(TopicUiState.Loading, viewModel.uiState.value.topicState) + + collectJob.cancel() } @Test fun uiStateTopic_whenFollowedIdsSuccessAndTopicSuccess_thenTopicSuccessAndNewsLoading() = runTest { - viewModel.uiState.test { - awaitItem() - topicsRepository.sendTopics(testInputTopics.map { it.topic }) - userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id)) - val item = awaitItem() - assertTrue(item.topicState is TopicUiState.Success) - assertTrue(item.newsState is NewsUiState.Loading) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + topicsRepository.sendTopics(testInputTopics.map { it.topic }) + userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id)) + val item = viewModel.uiState.value + assertTrue(item.topicState is TopicUiState.Success) + assertTrue(item.newsState is NewsUiState.Loading) + + collectJob.cancel() } @Test fun uiStateTopic_whenFollowedIdsSuccessAndTopicSuccessAndNewsIsSuccess_thenAllSuccess() = runTest { - viewModel.uiState.test { - awaitItem() - topicsRepository.sendTopics(testInputTopics.map { it.topic }) - userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id)) - newsRepository.sendNewsResources(sampleNewsResources) - val item = awaitItem() - assertTrue(item.topicState is TopicUiState.Success) - assertTrue(item.newsState is NewsUiState.Success) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + topicsRepository.sendTopics(testInputTopics.map { it.topic }) + userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id)) + newsRepository.sendNewsResources(sampleNewsResources) + val item = viewModel.uiState.value + assertTrue(item.topicState is TopicUiState.Success) + assertTrue(item.newsState is NewsUiState.Success) + + collectJob.cancel() } @Test fun uiStateTopic_whenFollowingTopic_thenShowUpdatedTopic() = runTest { - viewModel.uiState - .test { - awaitItem() - topicsRepository.sendTopics(testInputTopics.map { it.topic }) - // Set which topic IDs are followed, not including 0. - userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id)) - - viewModel.followTopicToggle(true) - - assertEquals( - TopicUiState.Success(followableTopic = testOutputTopics[0]), - awaitItem().topicState - ) - } + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() } + + topicsRepository.sendTopics(testInputTopics.map { it.topic }) + // Set which topic IDs are followed, not including 0. + userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id)) + + viewModel.followTopicToggle(true) + + assertEquals( + TopicUiState.Success(followableTopic = testOutputTopics[0]), + viewModel.uiState.value.topicState + ) + + collectJob.cancel() } }