diff options
Diffstat (limited to 'android/app/src/test')
| -rw-r--r-- | android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt | 154 |
1 files changed, 88 insertions, 66 deletions
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt index 4ba80511c6..73bfd1ef38 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt @@ -2,58 +2,48 @@ package net.mullvad.mullvadvpn.viewmodel import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test +import app.cash.turbine.turbineScope import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.verify -import junit.framework.Assert.assertEquals import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain -import net.mullvad.mullvadvpn.lib.ipc.Event +import net.mullvad.mullvadvpn.compose.state.LoginError +import net.mullvad.mullvadvpn.compose.state.LoginState.* +import net.mullvad.mullvadvpn.compose.state.LoginUiState +import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.model.AccountCreationResult import net.mullvad.mullvadvpn.model.AccountHistory +import net.mullvad.mullvadvpn.model.AccountToken import net.mullvad.mullvadvpn.model.DeviceListEvent import net.mullvad.mullvadvpn.model.LoginResult import net.mullvad.mullvadvpn.repository.AccountRepository import net.mullvad.mullvadvpn.repository.DeviceRepository -import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer -import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState +import org.junit.Assert.assertEquals import org.junit.Before +import org.junit.Rule import org.junit.Test class LoginViewModelTest { + @get:Rule val testCoroutineRule = TestCoroutineRule() @MockK private lateinit var mockedAccountRepository: AccountRepository - @MockK private lateinit var mockedDeviceRepository: DeviceRepository - @MockK private lateinit var mockedServiceConnectionContainer: ServiceConnectionContainer - private lateinit var loginViewModel: LoginViewModel - - private val accountCreationTestEvents = MutableSharedFlow<AccountCreationResult>() private val accountHistoryTestEvents = MutableStateFlow<AccountHistory>(AccountHistory.Missing) - private val loginTestEvents = MutableSharedFlow<Event.LoginEvent>() - - private val serviceConnectionState = - MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected) @Before fun setup() { Dispatchers.setMain(UnconfinedTestDispatcher()) MockKAnnotations.init(this, relaxUnitFun = true) - every { mockedAccountRepository.accountCreationEvents } returns accountCreationTestEvents - every { mockedAccountRepository.accountHistoryEvents } returns accountHistoryTestEvents - every { mockedAccountRepository.loginEvents } returns loginTestEvents - - serviceConnectionState.value = - ServiceConnectionState.ConnectedReady(mockedServiceConnectionContainer) + every { mockedAccountRepository.accountHistory } returns accountHistoryTestEvents loginViewModel = LoginViewModel( @@ -64,103 +54,135 @@ class LoginViewModelTest { } @Test - fun testDefaultState() = runBlockingTest { - loginViewModel.uiState.test { - assertEquals(LoginViewModel.LoginUiState.Default, awaitItem()) - } + fun testDefaultState() = runTest { + loginViewModel.uiState.test { assertEquals(LoginUiState.INITIAL, awaitItem()) } } @Test - fun testCreateAccount() = runBlockingTest { - loginViewModel.uiState.test { - skipDefaultItem() - loginViewModel.createAccount() - assertEquals(LoginViewModel.LoginUiState.CreatingAccount, awaitItem()) - accountCreationTestEvents.emit(AccountCreationResult.Success(DUMMY_ACCOUNT_TOKEN)) + fun testCreateAccount() = runTest { + turbineScope { + // Arrange + val uiStates = loginViewModel.uiState.testIn(backgroundScope) + val sideEffects = loginViewModel.viewActions.testIn(backgroundScope) + coEvery { mockedAccountRepository.createAccount() } returns + AccountCreationResult.Success(DUMMY_ACCOUNT_TOKEN) - assertEquals(LoginViewModel.LoginUiState.AccountCreated, awaitItem()) + // Act, Assert + uiStates.skipDefaultItem() + loginViewModel.createAccount() + assertEquals(Loading.CreatingAccount, uiStates.awaitItem().loginState) + assertEquals(LoginViewAction.NavigateToWelcome, sideEffects.awaitItem()) } } @Test - fun testLoginWithValidAccount() = runBlockingTest { - loginViewModel.uiState.test { - skipDefaultItem() + fun testLoginWithValidAccount() = runTest { + turbineScope { + // Arrange + val uiStates = loginViewModel.uiState.testIn(backgroundScope) + val sideEffects = loginViewModel.viewActions.testIn(backgroundScope) + coEvery { mockedAccountRepository.login(any()) } returns LoginResult.Ok + + // Act, Assert + uiStates.skipDefaultItem() loginViewModel.login(DUMMY_ACCOUNT_TOKEN) - assertEquals(LoginViewModel.LoginUiState.Loading, awaitItem()) - loginTestEvents.emit(Event.LoginEvent(LoginResult.Ok)) - assertEquals(LoginViewModel.LoginUiState.Success(isOutOfTime = false), awaitItem()) + assertEquals(Loading.LoggingIn, uiStates.awaitItem().loginState) + assertEquals(Success, uiStates.awaitItem().loginState) + assertEquals(LoginViewAction.NavigateToConnect, sideEffects.awaitItem()) } } @Test - fun testLoginWithInvalidAccount() = runBlockingTest { + fun testLoginWithInvalidAccount() = runTest { loginViewModel.uiState.test { + // Arrange + coEvery { mockedAccountRepository.login(any()) } returns LoginResult.InvalidAccount + + // Act, Assert skipDefaultItem() loginViewModel.login(DUMMY_ACCOUNT_TOKEN) - assertEquals(LoginViewModel.LoginUiState.Loading, awaitItem()) - loginTestEvents.emit(Event.LoginEvent(LoginResult.InvalidAccount)) - assertEquals(LoginViewModel.LoginUiState.InvalidAccountError, awaitItem()) + assertEquals(Loading.LoggingIn, awaitItem().loginState) + assertEquals(Idle(loginError = LoginError.InvalidCredentials), awaitItem().loginState) } } @Test - fun testLoginWithTooManyDevicesError() = runBlockingTest { - coEvery { - mockedDeviceRepository.refreshAndAwaitDeviceListWithTimeout(any(), any(), any(), any()) - } returns DeviceListEvent.Available(DUMMY_ACCOUNT_TOKEN, listOf()) + fun testLoginWithTooManyDevicesError() = runTest { + turbineScope { + // Arrange + val uiStates = loginViewModel.uiState.testIn(backgroundScope) + val sideEffects = loginViewModel.viewActions.testIn(backgroundScope) + coEvery { + mockedDeviceRepository.refreshAndAwaitDeviceListWithTimeout( + any(), + any(), + any(), + any() + ) + } returns DeviceListEvent.Available(DUMMY_ACCOUNT_TOKEN, listOf()) + coEvery { mockedAccountRepository.login(any()) } returns LoginResult.MaxDevicesReached - loginViewModel.uiState.test { - skipDefaultItem() + // Act, Assert + uiStates.skipDefaultItem() loginViewModel.login(DUMMY_ACCOUNT_TOKEN) - assertEquals(LoginViewModel.LoginUiState.Loading, awaitItem()) - loginTestEvents.emit(Event.LoginEvent(LoginResult.MaxDevicesReached)) + assertEquals(Loading.LoggingIn, uiStates.awaitItem().loginState) assertEquals( - LoginViewModel.LoginUiState.TooManyDevicesError(DUMMY_ACCOUNT_TOKEN), - awaitItem() + LoginViewAction.TooManyDevices(AccountToken(DUMMY_ACCOUNT_TOKEN)), + sideEffects.awaitItem() ) } } @Test - fun testLoginWithRpcError() = runBlockingTest { + fun testLoginWithRpcError() = runTest { loginViewModel.uiState.test { + // Arrange + coEvery { mockedAccountRepository.login(any()) } returns LoginResult.RpcError + + // Act, Assert skipDefaultItem() loginViewModel.login(DUMMY_ACCOUNT_TOKEN) - assertEquals(LoginViewModel.LoginUiState.Loading, awaitItem()) - loginTestEvents.emit(Event.LoginEvent(LoginResult.RpcError)) + assertEquals(Loading.LoggingIn, awaitItem().loginState) assertEquals( - LoginViewModel.LoginUiState.OtherError(EXPECTED_RPC_ERROR_MESSAGE), - awaitItem() + Idle(LoginError.Unknown(EXPECTED_RPC_ERROR_MESSAGE)), + awaitItem().loginState ) } } @Test - fun testLoginWithUnknownError() = runBlockingTest { + fun testLoginWithUnknownError() = runTest { loginViewModel.uiState.test { + // Arrange + coEvery { mockedAccountRepository.login(any()) } returns LoginResult.OtherError + + // Act, Assert skipDefaultItem() loginViewModel.login(DUMMY_ACCOUNT_TOKEN) - assertEquals(LoginViewModel.LoginUiState.Loading, awaitItem()) - loginTestEvents.emit(Event.LoginEvent(LoginResult.OtherError)) + assertEquals(Loading.LoggingIn, awaitItem().loginState) assertEquals( - LoginViewModel.LoginUiState.OtherError(EXPECTED_OTHER_ERROR_MESSAGE), - awaitItem() + Idle(LoginError.Unknown(EXPECTED_OTHER_ERROR_MESSAGE)), + awaitItem().loginState ) } } @Test - fun testAccountHistory() = runBlockingTest { - loginViewModel.accountHistory.test { + fun testAccountHistory() = runTest { + loginViewModel.uiState.test { + // Act, Assert skipDefaultItem() accountHistoryTestEvents.emit(AccountHistory.Available(DUMMY_ACCOUNT_TOKEN)) - assertEquals(AccountHistory.Available(DUMMY_ACCOUNT_TOKEN), awaitItem()) + assertEquals( + LoginUiState.INITIAL.copy(lastUsedAccount = AccountToken(DUMMY_ACCOUNT_TOKEN)), + awaitItem() + ) } } @Test - fun testClearingAccountHistory() = runBlockingTest { + fun testClearingAccountHistory() = runTest { + // Act, Assert loginViewModel.clearAccountHistory() verify { mockedAccountRepository.clearAccountHistory() } } |
