Merge "Update Main dispatcher rule, assert on StateFlow's value property" into main

pull/152/head
Jolanda Verhoef 2 years ago committed by Gerrit Code Review
commit 70806f6104

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

@ -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,13 +68,13 @@ class AuthorViewModelTest {
@Test
fun uiStateAuthor_whenSuccess_matchesAuthorFromRepository() = runTest {
viewModel.uiState.test {
awaitItem()
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
// To make sure AuthorUiState is success
authorsRepository.sendAuthors(testInputAuthors.map(FollowableAuthor::author))
userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id))
val item = awaitItem()
val item = viewModel.uiState.value
assertTrue(item.authorState is AuthorUiState.Success)
val successAuthorUiState = item.authorState as AuthorUiState.Success
@ -78,63 +84,63 @@ class AuthorViewModelTest {
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 {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id))
assertEquals(AuthorUiState.Loading, awaitItem().authorState)
}
assertEquals(AuthorUiState.Loading, viewModel.uiState.value.authorState)
collectJob.cancel()
}
@Test
fun uiStateAuthor_whenFollowedIdsSuccessAndAuthorSuccess_thenAuthorSuccessAndNewsLoading() =
runTest {
viewModel.uiState.test {
awaitItem()
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
authorsRepository.sendAuthors(testInputAuthors.map { it.author })
userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id))
val item = awaitItem()
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()
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 = awaitItem()
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()
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))
@ -143,9 +149,10 @@ class AuthorViewModelTest {
assertEquals(
AuthorUiState.Success(followableAuthor = testOutputAuthors[0]),
awaitItem().authorState
viewModel.uiState.value.authorState
)
}
collectJob.cancel()
}
}

@ -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,35 +60,36 @@ 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())
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())
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 })
@ -90,7 +97,7 @@ class InterestsViewModelTest {
assertEquals(
false,
(awaitItem() as InterestsUiState.Interests)
(viewModel.uiState.value as InterestsUiState.Interests)
.topics.first { it.topic.id == toggleTopicId }.isFollowed
)
@ -101,22 +108,21 @@ class InterestsViewModelTest {
assertEquals(
InterestsUiState.Interests(topics = testOutputTopics, authors = emptyList()),
awaitItem()
viewModel.uiState.value
)
}
collectJob.cancel()
}
@Test
fun uiState_whenFollowingNewAuthor_thenShowUpdatedAuthors() = runTest {
viewModel.uiState
.test {
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())
awaitItem()
viewModel.followAuthor(
followedAuthorId = testInputAuthors[1].author.id,
followed = true
@ -124,17 +130,18 @@ class InterestsViewModelTest {
assertEquals(
InterestsUiState.Interests(topics = emptyList(), authors = testOutputAuthors),
awaitItem()
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 })
@ -144,7 +151,7 @@ class InterestsViewModelTest {
assertEquals(
true,
(awaitItem() as InterestsUiState.Interests)
(viewModel.uiState.value as InterestsUiState.Interests)
.topics.first { it.topic.id == toggleTopicId }.isFollowed
)
@ -155,16 +162,16 @@ class InterestsViewModelTest {
assertEquals(
InterestsUiState.Interests(topics = testInputTopics, authors = emptyList()),
awaitItem()
viewModel.uiState.value
)
}
collectJob.cancel()
}
@Test
fun uiState_whenUnfollowingAuthors_thenShowUpdatedAuthors() = runTest {
viewModel.uiState
.test {
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)
@ -172,7 +179,6 @@ class InterestsViewModelTest {
topicsRepository.sendTopics(listOf())
userDataRepository.setFollowedTopicIds(setOf())
awaitItem()
viewModel.followAuthor(
followedAuthorId = testOutputAuthors[1].author.id,
followed = false
@ -180,9 +186,10 @@ class InterestsViewModelTest {
assertEquals(
InterestsUiState.Interests(topics = emptyList(), authors = testInputAuthors),
awaitItem()
viewModel.uiState.value
)
}
collectJob.cancel()
}
}

@ -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,11 +65,11 @@ class TopicViewModelTest {
@Test
fun uiStateAuthor_whenSuccess_matchesTopicFromRepository() = runTest {
viewModel.uiState.test {
awaitItem()
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
topicsRepository.sendTopics(testInputTopics.map(FollowableTopic::topic))
userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id))
val item = awaitItem()
val item = viewModel.uiState.value
assertTrue(item.topicState is TopicUiState.Success)
val successTopicState = item.topicState as TopicUiState.Success
@ -72,63 +78,63 @@ class TopicViewModelTest {
).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 {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id))
assertEquals(TopicUiState.Loading, awaitItem().topicState)
}
assertEquals(TopicUiState.Loading, viewModel.uiState.value.topicState)
collectJob.cancel()
}
@Test
fun uiStateTopic_whenFollowedIdsSuccessAndTopicSuccess_thenTopicSuccessAndNewsLoading() =
runTest {
viewModel.uiState.test {
awaitItem()
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
topicsRepository.sendTopics(testInputTopics.map { it.topic })
userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id))
val item = awaitItem()
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()
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 = awaitItem()
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()
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))
@ -137,9 +143,10 @@ class TopicViewModelTest {
assertEquals(
TopicUiState.Success(followableTopic = testOutputTopics[0]),
awaitItem().topicState
viewModel.uiState.value.topicState
)
}
collectJob.cancel()
}
}

Loading…
Cancel
Save