Skip to content

Commit

Permalink
Add view model and ui test
Browse files Browse the repository at this point in the history
  • Loading branch information
sabercodic committed Nov 17, 2023
1 parent 35d2ab7 commit aaa4a60
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package net.mullvad.mullvadvpn.compose.screen

import android.content.res.Resources
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import net.mullvad.mullvadvpn.compose.setContentWithTheme
import net.mullvad.mullvadvpn.compose.state.VoucherDialogUiState
import net.mullvad.mullvadvpn.compose.test.VOUCHER_INPUT_TEST_TAG
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
import net.mullvad.mullvadvpn.util.VoucherRegexHelper
import net.mullvad.mullvadvpn.viewmodel.VoucherDialogViewModel
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class RedeemVoucherDialogTest {
@get:Rule val composeTestRule = createComposeRule()

private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
private val mockResources: Resources = mockk()

private val _connectionState =
MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)

val connectionState = _connectionState.asStateFlow()

@Before
fun setup() {
MockKAnnotations.init(this)
mockkObject(VoucherRegexHelper)
}

@Test
fun testInsertInvalidVoucher() {
// Arrange
every { VoucherRegexHelper.validate(any()) } returns false
every { mockServiceConnectionManager.connectionState } returns connectionState

// Act
val vm = VoucherDialogViewModel(mockServiceConnectionManager, mockResources)
val uiState = vm.uiState.value
composeTestRule.setContentWithTheme {
RedeemVoucherDialogScreen(
uiState = uiState,
onVoucherInputChange = { vm.onVoucherInputChange(it) },
onRedeem = {},
onDismiss = {}
)
}

// Sets the TextField value
composeTestRule.onNodeWithTag(VOUCHER_INPUT_TEST_TAG).performTextInput(DUMMY_VALID_VOUCHER)
composeTestRule.onNodeWithText(REDEEM_BUTTON_TEXT).performClick()

// Assert
composeTestRule.onNodeWithText(DUMMY_INVALID_VOUCHER).assertDoesNotExist()
}

@Test
fun testDismissDialog() {
// Arrange
val mockedClickHandler: (Boolean) -> Unit = mockk(relaxed = true)

// Act
composeTestRule.setContentWithTheme {
RedeemVoucherDialogScreen(
uiState = VoucherDialogUiState.INITIAL,
onVoucherInputChange = {},
onRedeem = {},
onDismiss = mockedClickHandler
)
}

composeTestRule.onNodeWithText(CANCEL_BUTTON_TEXT).performClick()

// Assert
verify { mockedClickHandler.invoke(false) }
}

companion object {
private const val REDEEM_BUTTON_TEXT = "Redeem"
private const val CANCEL_BUTTON_TEXT = "Cancel"
// private const val DUMMY_VALID_VOUCHER = "DUMM-YVAL-IDVO-UCHE-R"
private const val DUMMY_VALID_VOUCHER = "DUMMYVALIDVOUCHER"
private const val DUMMY_USED_VOUCHER = "DUMM-YUSE-DVOU-CHER"
private const val DUMMY_INVALID_VOUCHER = "DUMM-YINV-ALID-VOUC-HER"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
Expand All @@ -29,6 +30,7 @@ import net.mullvad.mullvadvpn.compose.button.VariantButton
import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorSmall
import net.mullvad.mullvadvpn.compose.state.VoucherDialogState
import net.mullvad.mullvadvpn.compose.state.VoucherDialogUiState
import net.mullvad.mullvadvpn.compose.test.VOUCHER_INPUT_TEST_TAG
import net.mullvad.mullvadvpn.compose.textfield.CustomTextField
import net.mullvad.mullvadvpn.compose.util.MAX_VOUCHER_LENGTH
import net.mullvad.mullvadvpn.compose.util.vouchersVisualTransformation
Expand Down Expand Up @@ -230,7 +232,9 @@ private fun EnterVoucherBody(
keyboardType = KeyboardType.Password,
placeholderText = stringResource(id = R.string.voucher_hint),
visualTransformation = vouchersVisualTransformation(),
isDigitsOnlyAllowed = false
isDigitsOnlyAllowed = false,
isEnabled = true,
modifier = Modifier.testTag(VOUCHER_INPUT_TEST_TAG)
)
Spacer(modifier = Modifier.height(Dimens.smallPadding))
Row(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ const val PLAY_PAYMENT_INFO_ICON_TEST_TAG = "play_payment_info_icon_test_tag"

const val LOGIN_TITLE_TEST_TAG = "login_title_test_tag"
const val LOGIN_INPUT_TEST_TAG = "login_input_test_tag"

// VoucherDialog
const val VOUCHER_INPUT_TEST_TAG = "voucher_input_test_tag"
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package net.mullvad.mullvadvpn.utils

import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.util.VoucherRegexHelper
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

@RunWith(Parameterized::class)
class RedeemVoucherHelperTest(private val isValid: Boolean, private val voucher: String) {
@get:Rule val testCoroutineRule = TestCoroutineRule()

companion object {
@JvmStatic
@Parameterized.Parameters
fun data(): Collection<Array<Any>> =
listOf(
arrayOf(true, acceptable_inputs_for_voucher[0]),
arrayOf(true, acceptable_inputs_for_voucher[1]),
arrayOf(true, acceptable_inputs_for_voucher[2]),
arrayOf(true, acceptable_inputs_for_voucher[3]),
arrayOf(true, acceptable_inputs_for_voucher[4]),
arrayOf(true, acceptable_inputs_for_voucher[5]),
arrayOf(true, acceptable_inputs_for_voucher[6]),
arrayOf(true, acceptable_inputs_for_voucher[7]),
arrayOf(false, non_acceptable_inputs_for_voucher[0]),
arrayOf(false, non_acceptable_inputs_for_voucher[1]),
arrayOf(false, non_acceptable_inputs_for_voucher[2])
)

private val acceptable_inputs_for_voucher =
arrayOf(
"1",
"a",
"A",
"AAAA",
"AAAABBBB11112222",
"AAAA BBBB 1111 2222",
"AAAA-AAAA-1111-2222\r",
"AAAA-AAAA-1111-2222\n",
)
private val non_acceptable_inputs_for_voucher =
arrayOf(
"@",
"AAAABBBBCCCCDDDD\t",
"AAAA_BBBB_CCCC_DDDD",
)
}

@Test
fun shouldReturnExpectedResultForVoucherCheck() {
assertThat(VoucherRegexHelper.validate(voucher), equalTo(isValid))
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,48 @@
package net.mullvad.mullvadvpn.viewmodel

import android.content.res.Resources
import app.cash.turbine.turbineScope
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import kotlin.test.assertIs
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.state.VoucherDialogState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.model.VoucherSubmission
import net.mullvad.mullvadvpn.model.VoucherSubmissionError
import net.mullvad.mullvadvpn.model.VoucherSubmissionResult
import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
import net.mullvad.mullvadvpn.ui.serviceconnection.VoucherRedeemer
import net.mullvad.mullvadvpn.ui.serviceconnection.voucherRedeemer
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test

class VoucherDialogViewModelTest {
@get:Rule val testCoroutineRule = TestCoroutineRule()

private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
private val mockVoucherRedeemer: VoucherRedeemer = mockk()
private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
private val mockVoucherSubmission: VoucherSubmission = mockk()
private val mockConnectionProxy: ConnectionProxy = mockk()
private val serviceConnectionState =
MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)

private val mockVoucherRedeemer: VoucherRedeemer = mockk()
private val mockResources: Resources = mockk()

private val mockVoucherSubmissionOkResult: VoucherSubmissionResult.Ok =
VoucherSubmissionResult.Ok(mockVoucherSubmission)
private val mockVoucherSubmissionErrorResult: VoucherSubmissionResult =
VoucherSubmissionResult.Error(VoucherSubmissionError.OtherError)

Expand All @@ -36,9 +51,9 @@ class VoucherDialogViewModelTest {
@Before
fun setUp() {
mockkStatic(CACHE_EXTENSION_CLASS)
every { mockServiceConnectionManager.connectionState.value.readyContainer() } returns
mockServiceConnectionContainer
every { mockServiceConnectionManager.connectionState } returns serviceConnectionState
every { mockServiceConnectionContainer.voucherRedeemer } returns mockVoucherRedeemer
every { mockServiceConnectionContainer.connectionProxy } returns mockConnectionProxy

viewModel =
VoucherDialogViewModel(
Expand All @@ -53,21 +68,46 @@ class VoucherDialogViewModelTest {
}

@Test
@Ignore("TODO: Fix this failing test and then enable it again.")
fun test_submit_invalid_voucher() = runTest {
val voucher = DUMMY_VALID_VOUCHER
fun testSubmitVoucher() = runTest {
val voucher = DUMMY_INVALID_VOUCHER
val dummyStringResource = DUMMY_STRING_RESOURCE
// Arrange
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
every { mockServiceConnectionManager.voucherRedeemer() } returns mockVoucherRedeemer
every { mockResources.getString(any()) } returns dummyStringResource
coEvery { mockVoucherRedeemer.submit(voucher) } returns mockVoucherSubmissionErrorResult
// Act, Assert
assertIs<VoucherDialogState.Default>(viewModel.uiState.value.voucherViewModelState)
viewModel.onRedeem(voucher)
coVerify(exactly = 1) { mockVoucherRedeemer.submit(voucher) }
}

@Test
fun testInsertValidVoucher() = runTest {
turbineScope {
val voucher = DUMMY_VALID_VOUCHER
val dummyStringResource = DUMMY_STRING_RESOURCE
// Arrange
val uiStates = viewModel.uiState.testIn(backgroundScope)
every { mockServiceConnectionManager.voucherRedeemer() } returns mockVoucherRedeemer
every { mockResources.getString(any()) } returns dummyStringResource
every { mockVoucherSubmission.timeAdded } returns 0
coEvery { mockVoucherRedeemer.submit(voucher) } returns mockVoucherSubmissionOkResult

// Act, Assert
viewModel.onRedeem(voucher)
Assert.assertEquals(
VoucherDialogState.Default,
uiStates.awaitItem().voucherViewModelState
)
}
}

companion object {
private const val CACHE_EXTENSION_CLASS = "net.mullvad.mullvadvpn.util.CacheExtensionsKt"
private const val DUMMY_VALID_VOUCHER = "DUMMY_VALID_VOUCHER"
private const val DUMMY_STRING_RESOURCE = "DUMMY_STRING_RESOURCE"
private const val DUMMY_VALID_VOUCHER = "dummy_valid_voucher"
private const val DUMMY_INVALID_VOUCHER = "dummy_invalid_voucher"
private const val DUMMY_STRING_RESOURCE = "dummy_string_resource"
}
}

0 comments on commit aaa4a60

Please sign in to comment.