Change test setup to generalised event state testing

This commit is contained in:
Wolf-Martell Montwé 2023-06-02 13:09:23 +02:00
parent fe86ef5878
commit 1f18130b5c
No known key found for this signature in database
GPG key ID: 6D45B21512ACBF72
6 changed files with 86 additions and 61 deletions

View file

@ -20,6 +20,13 @@ internal class AccountOptionsViewModel(
initialState: State = State(),
private val validator: Validator,
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
override fun initState(state: State) {
updateState {
state.copy()
}
}
override fun event(event: Event) {
when (event) {
is OnAccountNameChanged -> updateState { state ->
@ -63,12 +70,6 @@ internal class AccountOptionsViewModel(
}
}
override fun initState(state: State) {
updateState {
state.copy()
}
}
private fun submit() = with(state.value) {
val accountNameResult = validator.validateAccountName(accountName.value)
val displayNameResult = validator.validateDisplayName(displayName.value)
@ -82,9 +83,9 @@ internal class AccountOptionsViewModel(
updateState {
it.copy(
accountName = state.value.accountName.updateFromValidationResult(accountNameResult),
displayName = state.value.displayName.updateFromValidationResult(displayNameResult),
emailSignature = state.value.emailSignature.updateFromValidationResult(emailSignatureResult),
accountName = it.accountName.updateFromValidationResult(accountNameResult),
displayName = it.displayName.updateFromValidationResult(displayNameResult),
emailSignature = it.emailSignature.updateFromValidationResult(emailSignatureResult),
)
}

View file

@ -0,0 +1,35 @@
package app.k9mail.feature.account.setup.testing
import app.cash.turbine.testIn
import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
import assertk.assertions.assertThatAndTurbinesConsumed
import assertk.assertions.isEqualTo
import kotlinx.coroutines.CoroutineScope
internal suspend fun <STATE, EVENT, EFFECT> eventStateTest(
viewModel: UnidirectionalViewModel<STATE, EVENT, EFFECT>,
initialState: STATE,
event: EVENT,
expectedState: STATE,
coroutineScope: CoroutineScope,
) {
val stateTurbine = viewModel.state.testIn(coroutineScope)
val effectTurbine = viewModel.effect.testIn(coroutineScope)
val turbines = listOf(stateTurbine, effectTurbine)
assertThatAndTurbinesConsumed(
actual = stateTurbine.awaitItem(),
turbines = turbines,
) {
isEqualTo(initialState)
}
viewModel.event(event)
assertThatAndTurbinesConsumed(
actual = stateTurbine.awaitItem(),
turbines = turbines,
) {
isEqualTo(expectedState)
}
}

View file

@ -11,7 +11,6 @@ import app.k9mail.feature.account.setup.ui.options.FakeAccountOptionsViewModel
import assertk.assertThat
import assertk.assertions.isEqualTo
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.test.runTest
import org.junit.Test
@ -35,7 +34,7 @@ class AccountSetupScreenKtTest : ComposeTest() {
}
for (step in SetupStep.values()) {
viewModel.mutableState.update { it.copy(setupStep = step) }
viewModel.state { it.copy(setupStep = step) }
onNodeWithTag(getTagForStep(step)).assertExists()
}
}
@ -62,12 +61,12 @@ class AccountSetupScreenKtTest : ComposeTest() {
assertThat(onFinishCounter).isEqualTo(0)
assertThat(onBackCounter).isEqualTo(0)
viewModel.mutableEffect.emit(Effect.NavigateNext)
viewModel.effect(Effect.NavigateNext)
assertThat(onFinishCounter).isEqualTo(1)
assertThat(onBackCounter).isEqualTo(0)
viewModel.mutableEffect.emit(Effect.NavigateBack)
viewModel.effect(Effect.NavigateBack)
assertThat(onFinishCounter).isEqualTo(1)
assertThat(onBackCounter).isEqualTo(1)

View file

@ -1,26 +1,25 @@
package app.k9mail.feature.account.setup.ui
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
import app.k9mail.feature.account.setup.ui.AccountSetupContract.Effect
import app.k9mail.feature.account.setup.ui.AccountSetupContract.Event
import app.k9mail.feature.account.setup.ui.AccountSetupContract.State
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
internal class FakeAccountSetupViewModel(
initialState: State = State(),
) : AccountSetupContract.ViewModel {
) : BaseViewModel<State, Event, Effect>(initialState), AccountSetupContract.ViewModel {
val mutableState = MutableStateFlow(initialState)
val mutableEffect = MutableSharedFlow<Effect>()
val events = mutableListOf<Event>()
override val state: StateFlow<State> = mutableState.asStateFlow()
override val effect: SharedFlow<Effect> = mutableEffect.asSharedFlow()
fun state(update: (State) -> (State)) {
updateState { update(it) }
}
override fun event(event: Event) {
events.add(event)
}
fun effect(effect: Effect) {
emitEffect(effect)
}
}

View file

@ -7,12 +7,13 @@ import app.k9mail.core.ui.compose.testing.MainDispatcherRule
import app.k9mail.feature.account.setup.domain.entity.EmailCheckFrequency
import app.k9mail.feature.account.setup.domain.entity.EmailDisplayCount
import app.k9mail.feature.account.setup.domain.input.StringInputField
import app.k9mail.feature.account.setup.testing.eventStateTest
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Effect
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Event
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.State
import assertk.assertThat
import assertk.assertions.assertThatAndTurbinesConsumed
import assertk.assertions.isEqualTo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@ -24,9 +25,15 @@ class AccountOptionsViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
private val testSubject = AccountOptionsViewModel(
validator = FakeAccountOptionsValidator(),
)
@Test
fun `should change state when OnAccountNameChanged event is received`() = runTest {
eventStateTest(
viewModel = testSubject,
initialState = State(),
event = Event.OnAccountNameChanged("accountName"),
expectedState = State(accountName = StringInputField(value = "accountName")),
coroutineScope = backgroundScope,
@ -36,6 +43,8 @@ class AccountOptionsViewModelTest {
@Test
fun `should change state when OnDisplayNameChanged event is received`() = runTest {
eventStateTest(
viewModel = testSubject,
initialState = State(),
event = Event.OnDisplayNameChanged("displayName"),
expectedState = State(displayName = StringInputField(value = "displayName")),
coroutineScope = backgroundScope,
@ -45,6 +54,8 @@ class AccountOptionsViewModelTest {
@Test
fun `should change state when OnEmailSignatureChanged event is received`() = runTest {
eventStateTest(
viewModel = testSubject,
initialState = State(),
event = Event.OnEmailSignatureChanged("emailSignature"),
expectedState = State(emailSignature = StringInputField(value = "emailSignature")),
coroutineScope = backgroundScope,
@ -54,6 +65,8 @@ class AccountOptionsViewModelTest {
@Test
fun `should change state when OnCheckFrequencyChanged event is received`() = runTest {
eventStateTest(
viewModel = testSubject,
initialState = State(),
event = Event.OnCheckFrequencyChanged(EmailCheckFrequency.EVERY_12_HOURS),
expectedState = State(checkFrequency = EmailCheckFrequency.EVERY_12_HOURS),
coroutineScope = backgroundScope,
@ -63,6 +76,8 @@ class AccountOptionsViewModelTest {
@Test
fun `should change state when OnMessageDisplayCountChanged event is received`() = runTest {
eventStateTest(
viewModel = testSubject,
initialState = State(),
event = Event.OnMessageDisplayCountChanged(EmailDisplayCount.MESSAGES_1000),
expectedState = State(messageDisplayCount = EmailDisplayCount.MESSAGES_1000),
coroutineScope = backgroundScope,
@ -72,6 +87,8 @@ class AccountOptionsViewModelTest {
@Test
fun `should change state when OnShowNotificationChanged event is received`() = runTest {
eventStateTest(
viewModel = testSubject,
initialState = State(),
event = Event.OnShowNotificationChanged(true),
expectedState = State(showNotification = true),
coroutineScope = backgroundScope,
@ -79,9 +96,9 @@ class AccountOptionsViewModelTest {
}
@Test
fun `should update state and emit NavigateNext effect when OnNextClicked event is received and input valid`() =
fun `should change state and emit NavigateNext effect when OnNextClicked event received and input valid`() =
runTest {
val viewModel = AccountOptionsViewModel(validator = FakeAccountOptionsValidator())
val viewModel = testSubject
val stateTurbine = viewModel.state.testIn(backgroundScope)
val effectTurbine = viewModel.effect.testIn(backgroundScope)
val turbines = listOf(stateTurbine, effectTurbine)
@ -107,12 +124,12 @@ class AccountOptionsViewModelTest {
actual = effectTurbine.awaitItem(),
turbines = turbines,
) {
isEqualTo(AccountOptionsContract.Effect.NavigateNext)
isEqualTo(Effect.NavigateNext)
}
}
@Test
fun `should update state and not emit effect when OnNextClicked event is received and input invalid`() =
fun `should change state and not emit effect when OnNextClicked event received and input invalid`() =
runTest {
val viewModel = AccountOptionsViewModel(
validator = FakeAccountOptionsValidator(
@ -147,8 +164,8 @@ class AccountOptionsViewModelTest {
}
@Test
fun `should emit NavigateBack effect when OnBackClicked event is received`() = runTest {
val viewModel = AccountOptionsViewModel(validator = FakeAccountOptionsValidator())
fun `should emit NavigateBack effect when OnBackClicked event received`() = runTest {
val viewModel = testSubject
val stateTurbine = viewModel.state.testIn(backgroundScope)
val effectTurbine = viewModel.effect.testIn(backgroundScope)
val turbines = listOf(stateTurbine, effectTurbine)
@ -166,34 +183,7 @@ class AccountOptionsViewModelTest {
actual = effectTurbine.awaitItem(),
turbines = turbines,
) {
isEqualTo(AccountOptionsContract.Effect.NavigateBack)
}
}
private suspend fun eventStateTest(
event: Event,
expectedState: State,
coroutineScope: CoroutineScope,
) {
val viewModel = AccountOptionsViewModel(validator = FakeAccountOptionsValidator())
val stateTurbine = viewModel.state.testIn(coroutineScope)
val effectTurbine = viewModel.effect.testIn(coroutineScope)
val turbines = listOf(stateTurbine, effectTurbine)
assertThatAndTurbinesConsumed(
actual = stateTurbine.awaitItem(),
turbines = turbines,
) {
isEqualTo(State())
}
viewModel.event(event)
assertThatAndTurbinesConsumed(
actual = stateTurbine.awaitItem(),
turbines = turbines,
) {
isEqualTo(expectedState)
isEqualTo(Effect.NavigateBack)
}
}

View file

@ -6,8 +6,9 @@ import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Event
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.State
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.ViewModel
class FakeAccountOptionsViewModel(initialState: State = State()) :
BaseViewModel<State, Event, Effect>(initialState), ViewModel {
class FakeAccountOptionsViewModel(
initialState: State = State(),
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
val events = mutableListOf<Event>()