diff --git a/.github/workflows/android-app.yml b/.github/workflows/android-app.yml index 97040b1cf5d6..70b5c3ca7dd2 100644 --- a/.github/workflows/android-app.yml +++ b/.github/workflows/android-app.yml @@ -220,6 +220,7 @@ jobs: testDebugUnitTest -x :test:arch:testDebugUnitTest :app:testOssProdDebugUnitTest :service:testOssProdDebugUnitTest + :lib:billing:testDebugUnitTest gradle-version: wrapper build-root-directory: android execution-only-caches: true diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index a49db334150a..e7403733d056 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,3 +1,4 @@ +import Dependencies.Plugin.ksp import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties import com.android.build.gradle.internal.tasks.factory.dependsOn import java.io.FileInputStream @@ -10,6 +11,7 @@ plugins { id(Dependencies.Plugin.kotlinAndroidId) id(Dependencies.Plugin.kotlinParcelizeId) id(Dependencies.Plugin.ksp) version Versions.Plugin.ksp + id(Dependencies.Plugin.junit5) version Versions.Plugin.junit5 } val repoRootPath = rootProject.projectDir.absoluteFile.parentFile.absolutePath @@ -349,21 +351,25 @@ dependencies { // Leak canary leakCanaryImplementation(Dependencies.leakCanary) - // Test dependencies + // Needed for createComposeExtension() and createAndroidComposeExtension() + debugImplementation(Dependencies.Compose.uiTestManifest) testImplementation(project(Dependencies.Mullvad.commonTestLib)) testImplementation(Dependencies.Kotlin.test) testImplementation(Dependencies.KotlinX.coroutinesTest) testImplementation(Dependencies.MockK.core) - testImplementation(Dependencies.junit) testImplementation(Dependencies.turbine) + testImplementation(Dependencies.junitApi) + testRuntimeOnly(Dependencies.junitEngine) + testImplementation(Dependencies.junitParams) // UI test dependencies debugImplementation(Dependencies.AndroidX.fragmentTestning) // Fixes: https://github.com/android/android-test/issues/1589 debugImplementation(Dependencies.AndroidX.testMonitor) debugImplementation(Dependencies.Compose.testManifest) - androidTestImplementation(Dependencies.Compose.junit) androidTestImplementation(Dependencies.Koin.test) androidTestImplementation(Dependencies.Kotlin.test) androidTestImplementation(Dependencies.MockK.android) + androidTestImplementation(Dependencies.junitApi) + androidTestImplementation(Dependencies.Compose.junit5) } diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/ComposeRuleExtensions.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/ComposeRuleExtensions.kt index 69cd530b19f1..7566051c4535 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/ComposeRuleExtensions.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/ComposeRuleExtensions.kt @@ -1,9 +1,9 @@ package net.mullvad.mullvadvpn.compose import androidx.compose.runtime.Composable -import androidx.compose.ui.test.junit4.ComposeContentTestRule +import de.mannodermaus.junit5.compose.ComposeContext import net.mullvad.mullvadvpn.lib.theme.AppTheme -fun ComposeContentTestRule.setContentWithTheme(content: @Composable () -> Unit) { +fun ComposeContext.setContentWithTheme(content: @Composable () -> Unit) { setContent { AppTheme { content() } } } diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomPortDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomPortDialogTest.kt index 43e385b65d2a..1fd106379d25 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomPortDialogTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomPortDialogTest.kt @@ -2,22 +2,26 @@ package net.mullvad.mullvadvpn.compose.dialog import android.annotation.SuppressLint import androidx.compose.runtime.Composable -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performTextInput +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import net.mullvad.mullvadvpn.compose.setContentWithTheme import net.mullvad.mullvadvpn.compose.test.CUSTOM_PORT_DIALOG_INPUT_TEST_TAG import net.mullvad.mullvadvpn.model.PortRange import net.mullvad.mullvadvpn.onNodeWithTagAndText -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension class CustomPortDialogTest { - @get:Rule val composeTestRule = createComposeRule() + @OptIn(ExperimentalTestApi::class) + @JvmField + @RegisterExtension + val composeExtension = createComposeExtension() - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) } @@ -40,23 +44,22 @@ class CustomPortDialogTest { } @Test - fun testShowWireguardCustomPortDialogInvalidInt() { - // Input a number to make sure that a too long number does not show and it does not crash - // the app + fun testShowWireguardCustomPortDialogInvalidInt() = + composeExtension.use { + // Input a number to make sure that a too long number does not show and it does not + // crash + // the app - // Arrange - composeTestRule.setContentWithTheme { testWireguardCustomPortDialog() } + // Arrange + setContentWithTheme { testWireguardCustomPortDialog() } - // Act - composeTestRule - .onNodeWithTag(CUSTOM_PORT_DIALOG_INPUT_TEST_TAG) - .performTextInput(invalidCustomPort) + // Act + onNodeWithTag(CUSTOM_PORT_DIALOG_INPUT_TEST_TAG).performTextInput(invalidCustomPort) - // Assert - composeTestRule - .onNodeWithTagAndText(CUSTOM_PORT_DIALOG_INPUT_TEST_TAG, invalidCustomPort) - .assertDoesNotExist() - } + // Assert + onNodeWithTagAndText(CUSTOM_PORT_DIALOG_INPUT_TEST_TAG, invalidCustomPort) + .assertDoesNotExist() + } companion object { const val invalidCustomPort = "21474836471" diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialogTest.kt index bc8d87b24493..5f9ffaea9563 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialogTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialogTest.kt @@ -2,16 +2,20 @@ package net.mullvad.mullvadvpn.compose.dialog import android.annotation.SuppressLint import androidx.compose.runtime.Composable +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsNotEnabled -import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText +import de.mannodermaus.junit5.compose.createComposeExtension import net.mullvad.mullvadvpn.compose.setContentWithTheme import net.mullvad.mullvadvpn.viewmodel.DnsDialogViewState -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension class DnsDialogTest { - @get:Rule val composeTestRule = createComposeRule() + @OptIn(ExperimentalTestApi::class) + @JvmField + @RegisterExtension + val composeExtension = createComposeExtension() private val defaultState = DnsDialogViewState( @@ -35,80 +39,86 @@ class DnsDialogTest { } @Test - fun testDnsDialogLanWarningShownWhenLanTrafficDisabledAndLocalAddressUsed() { - // Arrange - composeTestRule.setContentWithTheme { - testDnsDialog(defaultState.copy(isAllowLanEnabled = false, isLocal = true)) + fun testDnsDialogLanWarningShownWhenLanTrafficDisabledAndLocalAddressUsed() = + composeExtension.use { + // Arrange + setContentWithTheme { + testDnsDialog(defaultState.copy(isAllowLanEnabled = false, isLocal = true)) + } + + // Assert + onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertExists() } - // Assert - composeTestRule.onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertExists() - } - @Test - fun testDnsDialogLanWarningNotShownWhenLanTrafficEnabledAndLocalAddressUsed() { - // Arrange - composeTestRule.setContentWithTheme { - testDnsDialog(defaultState.copy(isAllowLanEnabled = true, isLocal = true)) + fun testDnsDialogLanWarningNotShownWhenLanTrafficEnabledAndLocalAddressUsed() = + composeExtension.use { + // Arrange + setContentWithTheme { + testDnsDialog(defaultState.copy(isAllowLanEnabled = true, isLocal = true)) + } + + // Assert + onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() } - // Assert - composeTestRule.onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() - } - @Test - fun testDnsDialogLanWarningNotShownWhenLanTrafficEnabledAndNonLocalAddressUsed() { - // Arrange - composeTestRule.setContentWithTheme { - testDnsDialog(defaultState.copy(isAllowLanEnabled = true, isLocal = false)) + fun testDnsDialogLanWarningNotShownWhenLanTrafficEnabledAndNonLocalAddressUsed() = + composeExtension.use { + // Arrange + setContentWithTheme { + testDnsDialog(defaultState.copy(isAllowLanEnabled = true, isLocal = false)) + } + + // Assert + onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() } - // Assert - composeTestRule.onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() - } - @Test - fun testDnsDialogLanWarningNotShownWhenLanTrafficDisabledAndNonLocalAddressUsed() { - // Arrange - composeTestRule.setContentWithTheme { - testDnsDialog(defaultState.copy(isAllowLanEnabled = false, isLocal = false)) + fun testDnsDialogLanWarningNotShownWhenLanTrafficDisabledAndNonLocalAddressUsed() = + composeExtension.use { + // Arrange + setContentWithTheme { + testDnsDialog(defaultState.copy(isAllowLanEnabled = false, isLocal = false)) + } + + // Assert + onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() } - // Assert - composeTestRule.onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() - } - @Test - fun testDnsDialogSubmitButtonDisabledOnInvalidDnsAddress() { - // Arrange - composeTestRule.setContentWithTheme { - testDnsDialog( - defaultState.copy( - ipAddress = invalidIpAddress, - validationResult = DnsDialogViewState.ValidationResult.InvalidAddress, + fun testDnsDialogSubmitButtonDisabledOnInvalidDnsAddress() = + composeExtension.use { + // Arrange + setContentWithTheme { + testDnsDialog( + defaultState.copy( + ipAddress = invalidIpAddress, + validationResult = DnsDialogViewState.ValidationResult.InvalidAddress, + ) ) - ) - } + } - // Assert - composeTestRule.onNodeWithText("Submit").assertIsNotEnabled() - } + // Assert + onNodeWithText("Submit").assertIsNotEnabled() + } @Test - fun testDnsDialogSubmitButtonDisabledOnDuplicateDnsAddress() { - // Arrange - composeTestRule.setContentWithTheme { - testDnsDialog( - defaultState.copy( - ipAddress = "192.168.0.1", - validationResult = DnsDialogViewState.ValidationResult.DuplicateAddress, + fun testDnsDialogSubmitButtonDisabledOnDuplicateDnsAddress() = + composeExtension.use { + // Arrange + setContentWithTheme { + testDnsDialog( + defaultState.copy( + ipAddress = "192.168.0.1", + validationResult = DnsDialogViewState.ValidationResult.DuplicateAddress, + ) ) - ) - } + } - // Assert - composeTestRule.onNodeWithText("Submit").assertIsNotEnabled() - } + // Assert + onNodeWithText("Submit").assertIsNotEnabled() + } companion object { private const val LOCAL_DNS_SERVER_WARNING = diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/MtuDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/MtuDialogTest.kt index 38a3bd170da3..28d089cc7e97 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/MtuDialogTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/MtuDialogTest.kt @@ -2,24 +2,28 @@ package net.mullvad.mullvadvpn.compose.dialog import android.annotation.SuppressLint import androidx.compose.runtime.Composable +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsNotEnabled -import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextInput +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import io.mockk.mockk import io.mockk.verify import net.mullvad.mullvadvpn.compose.setContentWithTheme -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension class MtuDialogTest { - @get:Rule val composeTestRule = createComposeRule() + @OptIn(ExperimentalTestApi::class) + @JvmField + @RegisterExtension + val composeExtension = createComposeExtension() - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) } @@ -41,110 +45,115 @@ class MtuDialogTest { } @Test - fun testMtuDialogWithDefaultValue() { - // Arrange - composeTestRule.setContentWithTheme { testMtuDialog() } + fun testMtuDialogWithDefaultValue() = + composeExtension.use { + // Arrange + setContentWithTheme { testMtuDialog() } - // Assert - composeTestRule.onNodeWithText(EMPTY_STRING).assertExists() - } + // Assert + onNodeWithText(EMPTY_STRING).assertExists() + } @Test - fun testMtuDialogWithEditValue() { - // Arrange - composeTestRule.setContentWithTheme { - testMtuDialog( - mtuInitial = VALID_DUMMY_MTU_VALUE, - ) + fun testMtuDialogWithEditValue() = + composeExtension.use { + // Arrange + setContentWithTheme { + testMtuDialog( + mtuInitial = VALID_DUMMY_MTU_VALUE, + ) + } + + // Assert + onNodeWithText(VALID_DUMMY_MTU_VALUE.toString()).assertExists() } - // Assert - composeTestRule.onNodeWithText(VALID_DUMMY_MTU_VALUE.toString()).assertExists() - } - @Test - fun testMtuDialogTextInput() { - // Arrange - composeTestRule.setContentWithTheme { - testMtuDialog( - null, - ) + fun testMtuDialogTextInput() = + composeExtension.use { + // Arrange + setContentWithTheme { + testMtuDialog( + null, + ) + } + + // Act + onNodeWithText(EMPTY_STRING).performTextInput(VALID_DUMMY_MTU_VALUE.toString()) + + // Assert + onNodeWithText(VALID_DUMMY_MTU_VALUE.toString()).assertExists() } - // Act - composeTestRule - .onNodeWithText(EMPTY_STRING) - .performTextInput(VALID_DUMMY_MTU_VALUE.toString()) - - // Assert - composeTestRule.onNodeWithText(VALID_DUMMY_MTU_VALUE.toString()).assertExists() - } - @Test - fun testMtuDialogSubmitOfValidValue() { - // Arrange - val mockedSubmitHandler: (Int) -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - testMtuDialog( - VALID_DUMMY_MTU_VALUE, - onSaveMtu = mockedSubmitHandler, - ) + fun testMtuDialogSubmitOfValidValue() = + composeExtension.use { + // Arrange + val mockedSubmitHandler: (Int) -> Unit = mockk(relaxed = true) + setContentWithTheme { + testMtuDialog( + VALID_DUMMY_MTU_VALUE, + onSaveMtu = mockedSubmitHandler, + ) + } + + // Act + onNodeWithText("Submit").assertIsEnabled().performClick() + + // Assert + verify { mockedSubmitHandler.invoke(VALID_DUMMY_MTU_VALUE) } } - // Act - composeTestRule.onNodeWithText("Submit").assertIsEnabled().performClick() - - // Assert - verify { mockedSubmitHandler.invoke(VALID_DUMMY_MTU_VALUE) } - } - @Test - fun testMtuDialogSubmitButtonDisabledWhenInvalidInput() { - // Arrange - composeTestRule.setContentWithTheme { - testMtuDialog( - INVALID_DUMMY_MTU_VALUE, - ) + fun testMtuDialogSubmitButtonDisabledWhenInvalidInput() = + composeExtension.use { + // Arrange + setContentWithTheme { + testMtuDialog( + INVALID_DUMMY_MTU_VALUE, + ) + } + + // Assert + onNodeWithText("Submit").assertIsNotEnabled() } - // Assert - composeTestRule.onNodeWithText("Submit").assertIsNotEnabled() - } - @Test - fun testMtuDialogResetClick() { - // Arrange - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - testMtuDialog( - onResetMtu = mockedClickHandler, - ) + fun testMtuDialogResetClick() = + composeExtension.use { + // Arrange + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + testMtuDialog( + onResetMtu = mockedClickHandler, + ) + } + + // Act + onNodeWithText("Reset to default").performClick() + + // Assert + verify { mockedClickHandler.invoke() } } - // Act - composeTestRule.onNodeWithText("Reset to default").performClick() - - // Assert - verify { mockedClickHandler.invoke() } - } - @Test - fun testMtuDialogCancelClick() { - // Arrange - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - testMtuDialog( - onDismiss = mockedClickHandler, - ) + fun testMtuDialogCancelClick() = + composeExtension.use { + // Arrange + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + testMtuDialog( + onDismiss = mockedClickHandler, + ) + } + + // Assert + onNodeWithText("Cancel").performClick() + + // Assert + verify { mockedClickHandler.invoke() } } - // Assert - composeTestRule.onNodeWithText("Cancel").performClick() - - // Assert - verify { mockedClickHandler.invoke() } - } - companion object { private const val EMPTY_STRING = "" private const val VALID_DUMMY_MTU_VALUE = 1337 diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/PaymentDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/PaymentDialogTest.kt index 1a626ecf1913..9012b3144f3a 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/PaymentDialogTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/PaymentDialogTest.kt @@ -1,57 +1,64 @@ package net.mullvad.mullvadvpn.compose.dialog -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText +import de.mannodermaus.junit5.compose.createComposeExtension import net.mullvad.mullvadvpn.compose.dialog.payment.PaymentDialog import net.mullvad.mullvadvpn.compose.setContentWithTheme import net.mullvad.mullvadvpn.lib.payment.model.ProductId import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult import net.mullvad.mullvadvpn.util.toPaymentDialogData -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension class PaymentDialogTest { - @get:Rule val composeTestRule = createComposeRule() + @OptIn(ExperimentalTestApi::class) + @JvmField + @RegisterExtension + val composeExtension = createComposeExtension() @Test - fun testShowPurchaseCompleteDialog() { - // Arrange - composeTestRule.setContentWithTheme { - PaymentDialog( - paymentDialogData = PurchaseResult.Completed.Success.toPaymentDialogData()!! - ) - } + fun testShowPurchaseCompleteDialog() = + composeExtension.use { + // Arrange + setContentWithTheme { + PaymentDialog( + paymentDialogData = PurchaseResult.Completed.Success.toPaymentDialogData()!! + ) + } - // Assert - composeTestRule.onNodeWithText("Time was successfully added").assertExists() - } + // Assert + onNodeWithText("Time was successfully added").assertExists() + } @Test - fun testShowVerificationErrorDialog() { - // Arrange - composeTestRule.setContentWithTheme { - PaymentDialog( - paymentDialogData = - PurchaseResult.Error.VerificationError(null).toPaymentDialogData()!! - ) - } + fun testShowVerificationErrorDialog() = + composeExtension.use { + // Arrange + setContentWithTheme { + PaymentDialog( + paymentDialogData = + PurchaseResult.Error.VerificationError(null).toPaymentDialogData()!! + ) + } - // Assert - composeTestRule.onNodeWithText("Verifying purchase").assertExists() - } + // Assert + onNodeWithText("Verifying purchase").assertExists() + } @Test - fun testShowFetchProductsErrorDialog() { - // Arrange - composeTestRule.setContentWithTheme { - PaymentDialog( - paymentDialogData = - PurchaseResult.Error.FetchProductsError(ProductId(""), null) - .toPaymentDialogData()!! - ) - } + fun testShowFetchProductsErrorDialog() = + composeExtension.use { + // Arrange + setContentWithTheme { + PaymentDialog( + paymentDialogData = + PurchaseResult.Error.FetchProductsError(ProductId(""), null) + .toPaymentDialogData()!! + ) + } - // Assert - composeTestRule.onNodeWithText("Google Play unavailable").assertExists() - } + // Assert + onNodeWithText("Google Play unavailable").assertExists() + } } diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt index 3b42cc1c3b10..bead0a02e57a 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt @@ -1,10 +1,11 @@ package net.mullvad.mullvadvpn.compose.screen import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk @@ -20,252 +21,272 @@ import net.mullvad.mullvadvpn.lib.payment.model.ProductId import net.mullvad.mullvadvpn.lib.payment.model.ProductPrice import net.mullvad.mullvadvpn.viewmodel.AccountUiState import net.mullvad.mullvadvpn.viewmodel.AccountViewModel -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +@ExperimentalTestApi @OptIn(ExperimentalMaterial3Api::class) class AccountScreenTest { - @get:Rule val composeTestRule = createComposeRule() + @JvmField @RegisterExtension val composeExtension = createComposeExtension() - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) } @Test - fun testDefaultState() { - // Arrange - composeTestRule.setContentWithTheme { - AccountScreen( - uiState = - AccountUiState( - deviceName = DUMMY_DEVICE_NAME, - accountNumber = DUMMY_ACCOUNT_NUMBER, - accountExpiry = null, - showSitePayment = false - ), - uiSideEffect = MutableSharedFlow().asSharedFlow(), - ) - } + fun testDefaultState() = + composeExtension.use { + // Arrange + setContentWithTheme { + AccountScreen( + uiState = + AccountUiState( + deviceName = DUMMY_DEVICE_NAME, + accountNumber = DUMMY_ACCOUNT_NUMBER, + accountExpiry = null, + showSitePayment = false + ), + uiSideEffect = + MutableSharedFlow().asSharedFlow(), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("Redeem voucher").assertExists() onNodeWithText("Log out").assertExists() } - } @Test - fun testManageAccountClick() { - // Arrange - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - AccountScreen( - uiState = - AccountUiState( - showSitePayment = true, - deviceName = DUMMY_DEVICE_NAME, - accountNumber = DUMMY_ACCOUNT_NUMBER, - accountExpiry = null, - ), - uiSideEffect = MutableSharedFlow().asSharedFlow(), - onManageAccountClick = mockedClickHandler - ) - } + fun testManageAccountClick() = + composeExtension.use { + // Arrange + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + AccountScreen( + uiState = + AccountUiState( + showSitePayment = true, + deviceName = DUMMY_DEVICE_NAME, + accountNumber = DUMMY_ACCOUNT_NUMBER, + accountExpiry = null, + ), + uiSideEffect = + MutableSharedFlow().asSharedFlow(), + onManageAccountClick = mockedClickHandler + ) + } - // Act - composeTestRule.onNodeWithText("Manage account").performClick() + // Act + onNodeWithText("Manage account").performClick() - // Assert - verify { mockedClickHandler.invoke() } - } + // Assert + verify(exactly = 1) { mockedClickHandler.invoke() } + } @Test - fun testRedeemVoucherClick() { - // Arrange - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - AccountScreen( - uiState = - AccountUiState( - deviceName = DUMMY_DEVICE_NAME, - accountNumber = DUMMY_ACCOUNT_NUMBER, - accountExpiry = null, - showSitePayment = false - ), - uiSideEffect = MutableSharedFlow().asSharedFlow(), - onRedeemVoucherClick = mockedClickHandler - ) - } + fun testRedeemVoucherClick() = + composeExtension.use { + // Arrange + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + AccountScreen( + uiState = + AccountUiState( + deviceName = DUMMY_DEVICE_NAME, + accountNumber = DUMMY_ACCOUNT_NUMBER, + accountExpiry = null, + showSitePayment = false + ), + uiSideEffect = + MutableSharedFlow().asSharedFlow(), + onRedeemVoucherClick = mockedClickHandler + ) + } - // Act - composeTestRule.onNodeWithText("Redeem voucher").performClick() + // Act + onNodeWithText("Redeem voucher").performClick() - // Assert - verify { mockedClickHandler.invoke() } - } + // Assert + verify { mockedClickHandler.invoke() } + } @Test - fun testLogoutClick() { - // Arrange - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - AccountScreen( - uiState = - AccountUiState( - deviceName = DUMMY_DEVICE_NAME, - accountNumber = DUMMY_ACCOUNT_NUMBER, - accountExpiry = null, - showSitePayment = false - ), - uiSideEffect = MutableSharedFlow().asSharedFlow(), - onLogoutClick = mockedClickHandler - ) - } + fun testLogoutClick() = + composeExtension.use { + // Arrange + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + AccountScreen( + uiState = + AccountUiState( + deviceName = DUMMY_DEVICE_NAME, + accountNumber = DUMMY_ACCOUNT_NUMBER, + accountExpiry = null, + showSitePayment = false + ), + uiSideEffect = + MutableSharedFlow().asSharedFlow(), + onLogoutClick = mockedClickHandler + ) + } - // Act - composeTestRule.onNodeWithText("Log out").performClick() + // Act + onNodeWithText("Log out").performClick() - // Assert - verify { mockedClickHandler.invoke() } - } + // Assert + verify { mockedClickHandler.invoke() } + } @Test - fun testShowBillingErrorPaymentButton() { - // Arrange - composeTestRule.setContentWithTheme { - AccountScreen( - uiState = - AccountUiState.default().copy(billingPaymentState = PaymentState.Error.Billing), - uiSideEffect = MutableSharedFlow().asSharedFlow(), - ) - } + fun testShowBillingErrorPaymentButton() = + composeExtension.use { + // Arrange + setContentWithTheme { + AccountScreen( + uiState = + AccountUiState.default() + .copy(billingPaymentState = PaymentState.Error.Billing), + uiSideEffect = + MutableSharedFlow().asSharedFlow(), + ) + } - // Assert - composeTestRule.onNodeWithText("Add 30 days time").assertExists() - } + // Assert + onNodeWithText("Add 30 days time").assertExists() + } @Test - fun testShowBillingPaymentAvailable() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns null - composeTestRule.setContentWithTheme { - AccountScreen( - uiState = - AccountUiState.default() - .copy( - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) - ), - uiSideEffect = MutableSharedFlow().asSharedFlow(), - ) - } + fun testShowBillingPaymentAvailable() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns null + setContentWithTheme { + AccountScreen( + uiState = + AccountUiState.default() + .copy( + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) + ), + uiSideEffect = + MutableSharedFlow().asSharedFlow(), + ) + } - // Assert - composeTestRule.onNodeWithText("Add 30 days time ($10)").assertExists() - } + // Assert + onNodeWithText("Add 30 days time ($10)").assertExists() + } @Test - fun testShowPendingPayment() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns PaymentStatus.PENDING - composeTestRule.setContentWithTheme { - AccountScreen( - uiState = - AccountUiState.default() - .copy( - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) - ), - uiSideEffect = MutableSharedFlow().asSharedFlow(), - ) - } + fun testShowPendingPayment() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns PaymentStatus.PENDING + setContentWithTheme { + AccountScreen( + uiState = + AccountUiState.default() + .copy( + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) + ), + uiSideEffect = + MutableSharedFlow().asSharedFlow(), + ) + } - // Assert - composeTestRule.onNodeWithText("Google Play payment pending").assertExists() - } + // Assert + onNodeWithText("Google Play payment pending").assertExists() + } @Test - fun testShowPendingPaymentInfoDialog() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns PaymentStatus.PENDING - val mockNavigateToVerificationPending: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - AccountScreen( - uiState = - AccountUiState.default() - .copy( - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) - ), - uiSideEffect = MutableSharedFlow().asSharedFlow(), - navigateToVerificationPendingDialog = mockNavigateToVerificationPending - ) - } + fun testShowPendingPaymentInfoDialog() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns PaymentStatus.PENDING + val mockNavigateToVerificationPending: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + AccountScreen( + uiState = + AccountUiState.default() + .copy( + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) + ), + uiSideEffect = + MutableSharedFlow().asSharedFlow(), + navigateToVerificationPendingDialog = mockNavigateToVerificationPending + ) + } - // Act - composeTestRule.onNodeWithTag(PLAY_PAYMENT_INFO_ICON_TEST_TAG).performClick() + // Act + onNodeWithTag(PLAY_PAYMENT_INFO_ICON_TEST_TAG).performClick() - // Assert - verify(exactly = 1) { mockNavigateToVerificationPending.invoke() } - } + // Assert + verify(exactly = 1) { mockNavigateToVerificationPending.invoke() } + } @Test - fun testShowVerificationInProgress() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns PaymentStatus.VERIFICATION_IN_PROGRESS - composeTestRule.setContentWithTheme { - AccountScreen( - uiState = - AccountUiState.default() - .copy( - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) - ), - uiSideEffect = MutableSharedFlow().asSharedFlow(), - ) - } + fun testShowVerificationInProgress() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns PaymentStatus.VERIFICATION_IN_PROGRESS + setContentWithTheme { + AccountScreen( + uiState = + AccountUiState.default() + .copy( + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) + ), + uiSideEffect = + MutableSharedFlow().asSharedFlow(), + ) + } - // Assert - composeTestRule.onNodeWithText("Verifying purchase").assertExists() - } + // Assert + onNodeWithText("Verifying purchase").assertExists() + } @Test - fun testOnPurchaseBillingProductClick() { - // Arrange - val clickHandler: (ProductId) -> Unit = mockk(relaxed = true) - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.productId } returns ProductId("PRODUCT_ID") - every { mockPaymentProduct.status } returns null - composeTestRule.setContentWithTheme { - AccountScreen( - uiState = - AccountUiState.default() - .copy( - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) - ), - onPurchaseBillingProductClick = clickHandler, - uiSideEffect = MutableSharedFlow().asSharedFlow(), - ) - } + fun testOnPurchaseBillingProductClick() = + composeExtension.use { + // Arrange + val clickHandler: (ProductId) -> Unit = mockk(relaxed = true) + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.productId } returns ProductId("PRODUCT_ID") + every { mockPaymentProduct.status } returns null + setContentWithTheme { + AccountScreen( + uiState = + AccountUiState.default() + .copy( + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) + ), + onPurchaseBillingProductClick = clickHandler, + uiSideEffect = + MutableSharedFlow().asSharedFlow(), + ) + } - // Act - composeTestRule.onNodeWithText("Add 30 days time ($10)").performClick() + // Act + onNodeWithText("Add 30 days time ($10)").performClick() - // Assert - verify { clickHandler.invoke(ProductId("PRODUCT_ID")) } - } + // Assert + verify { clickHandler.invoke(ProductId("PRODUCT_ID")) } + } companion object { private const val DUMMY_DEVICE_NAME = "fake_name" diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ChangelogDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ChangelogDialogTest.kt index 4e34fe082525..66ed6d25f65d 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ChangelogDialogTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ChangelogDialogTest.kt @@ -1,8 +1,9 @@ package net.mullvad.mullvadvpn.compose.screen -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.every @@ -13,44 +14,47 @@ import net.mullvad.mullvadvpn.compose.dialog.ChangelogDialog import net.mullvad.mullvadvpn.compose.setContentWithTheme import net.mullvad.mullvadvpn.viewmodel.Changelog import net.mullvad.mullvadvpn.viewmodel.ChangelogViewModel -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +@OptIn(ExperimentalTestApi::class) class ChangelogDialogTest { - @get:Rule val composeTestRule = createComposeRule() + @JvmField @RegisterExtension val composeExtension = createComposeExtension() @MockK lateinit var mockedViewModel: ChangelogViewModel - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) } @Test - fun testShowChangelogWhenNeeded() { - // Arrange - every { mockedViewModel.markChangelogAsRead() } just Runs - - composeTestRule.setContentWithTheme { - ChangelogDialog( - Changelog( - changes = listOf(CHANGELOG_ITEM), - version = CHANGELOG_VERSION, - ), - onDismiss = { mockedViewModel.markChangelogAsRead() } - ) - } + fun testShowChangeLogWhenNeeded() = + composeExtension.use { + // Arrange + // Arrange + every { mockedViewModel.markChangelogAsRead() } just Runs - // Check changelog content showed within dialog - composeTestRule.onNodeWithText(CHANGELOG_ITEM).assertExists() + setContentWithTheme { + ChangelogDialog( + Changelog( + changes = listOf(CHANGELOG_ITEM), + version = CHANGELOG_VERSION, + ), + onDismiss = { mockedViewModel.markChangelogAsRead() } + ) + } - // perform click on Got It button to check if dismiss occur - composeTestRule.onNodeWithText(CHANGELOG_BUTTON_TEXT).performClick() + // Check changelog content showed within dialog + onNodeWithText(CHANGELOG_ITEM).assertExists() - // Assert - verify { mockedViewModel.markChangelogAsRead() } - } + // perform click on Got It button to check if dismiss occur + onNodeWithText(CHANGELOG_BUTTON_TEXT).performClick() + + // Assert + verify { mockedViewModel.markChangelogAsRead() } + } companion object { private const val CHANGELOG_BUTTON_TEXT = "Got it!" diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt index 3838bdc7a0f2..39e11a3c142a 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt @@ -1,9 +1,10 @@ package net.mullvad.mullvadvpn.compose.screen -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk @@ -30,35 +31,38 @@ import net.mullvad.talpid.tunnel.ActionAfterDisconnect import net.mullvad.talpid.tunnel.ErrorState import net.mullvad.talpid.tunnel.ErrorStateCause import org.joda.time.DateTime -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension class ConnectScreenTest { - @get:Rule val composeTestRule = createComposeRule() + @OptIn(ExperimentalTestApi::class) + @JvmField + @RegisterExtension + val composeExtension = createComposeExtension() - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) } - @After + @AfterEach fun teardown() { unmockkAll() } @Test fun testDefaultState() { - // Arrange - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = ConnectUiState.INITIAL, - ) - } + composeExtension.use { + // Arrange + setContentWithTheme { + ConnectScreen( + uiState = ConnectUiState.INITIAL, + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithTag(SCROLLABLE_COLUMN_TEST_TAG).assertExists() onNodeWithText("UNSECURED CONNECTION").assertExists() onNodeWithText("Secure my connection").assertExists() @@ -67,28 +71,28 @@ class ConnectScreenTest { @Test fun testConnectingState() { - // Arrange - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connecting(null, null), - tunnelRealState = TunnelState.Connecting(null, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = InAppNotification.TunnelStateBlocked, - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Connecting(null, null), + tunnelRealState = TunnelState.Connecting(null, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = InAppNotification.TunnelStateBlocked, + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithTag(CIRCULAR_PROGRESS_INDICATOR).assertExists() onNodeWithText("CREATING SECURE CONNECTION").assertExists() onNodeWithText("Switch location").assertExists() @@ -99,31 +103,32 @@ class ConnectScreenTest { @Test fun testConnectingStateQuantumSecured() { - // Arrange - val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) - every { mockTunnelEndpoint.quantumResistant } returns true - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connecting(endpoint = mockTunnelEndpoint, null), - tunnelRealState = - TunnelState.Connecting(endpoint = mockTunnelEndpoint, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = InAppNotification.TunnelStateBlocked, - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) + every { mockTunnelEndpoint.quantumResistant } returns true + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = + TunnelState.Connecting(endpoint = mockTunnelEndpoint, null), + tunnelRealState = + TunnelState.Connecting(endpoint = mockTunnelEndpoint, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = InAppNotification.TunnelStateBlocked, + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithTag(CIRCULAR_PROGRESS_INDICATOR).assertExists() onNodeWithText("CREATING QUANTUM SECURE CONNECTION").assertExists() onNodeWithText("Switch location").assertExists() @@ -134,29 +139,29 @@ class ConnectScreenTest { @Test fun testConnectedState() { - // Arrange - val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connected(mockTunnelEndpoint, null), - tunnelRealState = TunnelState.Connected(mockTunnelEndpoint, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = null, - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Connected(mockTunnelEndpoint, null), + tunnelRealState = TunnelState.Connected(mockTunnelEndpoint, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = null, + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("SECURE CONNECTION").assertExists() onNodeWithText("Switch location").assertExists() onNodeWithText("Disconnect").assertExists() @@ -165,30 +170,30 @@ class ConnectScreenTest { @Test fun testConnectedStateQuantumSecured() { - // Arrange - val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) - every { mockTunnelEndpoint.quantumResistant } returns true - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connected(mockTunnelEndpoint, null), - tunnelRealState = TunnelState.Connected(mockTunnelEndpoint, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = null, - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) + every { mockTunnelEndpoint.quantumResistant } returns true + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Connected(mockTunnelEndpoint, null), + tunnelRealState = TunnelState.Connected(mockTunnelEndpoint, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = null, + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("QUANTUM SECURE CONNECTION").assertExists() onNodeWithText("Switch location").assertExists() onNodeWithText("Disconnect").assertExists() @@ -197,31 +202,33 @@ class ConnectScreenTest { @Test fun testDisconnectingState() { - // Arrange - val mockRelayLocation: RelayItem = mockk(relaxed = true) - val mockLocationName = "Home" - every { mockRelayLocation.locationName } returns mockLocationName - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = mockRelayLocation, - tunnelUiState = TunnelState.Disconnecting(ActionAfterDisconnect.Nothing), - tunnelRealState = TunnelState.Disconnecting(ActionAfterDisconnect.Nothing), - inAddress = null, - outAddress = "", - showLocation = true, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = null, - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val mockRelayLocation: RelayItem = mockk(relaxed = true) + val mockLocationName = "Home" + every { mockRelayLocation.locationName } returns mockLocationName + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = mockRelayLocation, + tunnelUiState = + TunnelState.Disconnecting(ActionAfterDisconnect.Nothing), + tunnelRealState = + TunnelState.Disconnecting(ActionAfterDisconnect.Nothing), + inAddress = null, + outAddress = "", + showLocation = true, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = null, + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("UNSECURED CONNECTION").assertExists() onNodeWithText(mockLocationName).assertExists() onNodeWithText("Disconnect").assertExists() @@ -230,31 +237,31 @@ class ConnectScreenTest { @Test fun testDisconnectedState() { - // Arrange - val mockRelayLocation: RelayItem = mockk(relaxed = true) - val mockLocationName = "Home" - every { mockRelayLocation.locationName } returns mockLocationName - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = mockRelayLocation, - tunnelUiState = TunnelState.Disconnected(), - tunnelRealState = TunnelState.Disconnected(), - inAddress = null, - outAddress = "", - showLocation = true, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = null, - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val mockRelayLocation: RelayItem = mockk(relaxed = true) + val mockLocationName = "Home" + every { mockRelayLocation.locationName } returns mockLocationName + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = mockRelayLocation, + tunnelUiState = TunnelState.Disconnected(), + tunnelRealState = TunnelState.Disconnected(), + inAddress = null, + outAddress = "", + showLocation = true, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = null, + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("UNSECURED CONNECTION").assertExists() onNodeWithText(mockLocationName).assertExists() onNodeWithText("Secure my connection").assertExists() @@ -263,36 +270,40 @@ class ConnectScreenTest { @Test fun testErrorStateBlocked() { - // Arrange - val mockRelayLocation: RelayItem = mockk(relaxed = true) - val mockLocationName = "Home" - every { mockRelayLocation.locationName } returns mockLocationName - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = mockRelayLocation, - tunnelUiState = - TunnelState.Error(ErrorState(ErrorStateCause.StartTunnelError, true)), - tunnelRealState = - TunnelState.Error(ErrorState(ErrorStateCause.StartTunnelError, true)), - inAddress = null, - outAddress = "", - showLocation = true, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = - InAppNotification.TunnelStateError( - ErrorState(ErrorStateCause.StartTunnelError, true) - ), - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val mockRelayLocation: RelayItem = mockk(relaxed = true) + val mockLocationName = "Home" + every { mockRelayLocation.locationName } returns mockLocationName + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = mockRelayLocation, + tunnelUiState = + TunnelState.Error( + ErrorState(ErrorStateCause.StartTunnelError, true) + ), + tunnelRealState = + TunnelState.Error( + ErrorState(ErrorStateCause.StartTunnelError, true) + ), + inAddress = null, + outAddress = "", + showLocation = true, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = + InAppNotification.TunnelStateError( + ErrorState(ErrorStateCause.StartTunnelError, true) + ), + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("BLOCKED CONNECTION").assertExists() onNodeWithText(mockLocationName).assertExists() onNodeWithText("Disconnect").assertExists() @@ -302,36 +313,40 @@ class ConnectScreenTest { @Test fun testErrorStateNotBlocked() { - // Arrange - val mockRelayLocation: RelayItem = mockk(relaxed = true) - val mockLocationName = "Home" - every { mockRelayLocation.locationName } returns mockLocationName - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = mockRelayLocation, - tunnelUiState = - TunnelState.Error(ErrorState(ErrorStateCause.StartTunnelError, false)), - tunnelRealState = - TunnelState.Error(ErrorState(ErrorStateCause.StartTunnelError, false)), - inAddress = null, - outAddress = "", - showLocation = true, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = - InAppNotification.TunnelStateError( - ErrorState(ErrorStateCause.StartTunnelError, false) - ), - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val mockRelayLocation: RelayItem = mockk(relaxed = true) + val mockLocationName = "Home" + every { mockRelayLocation.locationName } returns mockLocationName + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = mockRelayLocation, + tunnelUiState = + TunnelState.Error( + ErrorState(ErrorStateCause.StartTunnelError, false) + ), + tunnelRealState = + TunnelState.Error( + ErrorState(ErrorStateCause.StartTunnelError, false) + ), + inAddress = null, + outAddress = "", + showLocation = true, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = + InAppNotification.TunnelStateError( + ErrorState(ErrorStateCause.StartTunnelError, false) + ), + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("FAILED TO SECURE CONNECTION").assertExists() onNodeWithText(mockLocationName).assertExists() onNodeWithText("Dismiss").assertExists() @@ -342,29 +357,30 @@ class ConnectScreenTest { @Test fun testReconnectingState() { - // Arrange - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Disconnecting(ActionAfterDisconnect.Reconnect), - tunnelRealState = - TunnelState.Disconnecting(ActionAfterDisconnect.Reconnect), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = InAppNotification.TunnelStateBlocked, - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = + TunnelState.Disconnecting(ActionAfterDisconnect.Reconnect), + tunnelRealState = + TunnelState.Disconnecting(ActionAfterDisconnect.Reconnect), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = InAppNotification.TunnelStateBlocked, + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithTag(CIRCULAR_PROGRESS_INDICATOR).assertExists() onNodeWithText("CREATING SECURE CONNECTION").assertExists() onNodeWithText("Switch location").assertExists() @@ -375,31 +391,32 @@ class ConnectScreenTest { @Test fun testDisconnectingBlockState() { - // Arrange - val mockRelayLocation: RelayItem = mockk(relaxed = true) - val mockLocationName = "Home" - every { mockRelayLocation.locationName } returns mockLocationName - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = mockRelayLocation, - tunnelUiState = TunnelState.Disconnecting(ActionAfterDisconnect.Block), - tunnelRealState = TunnelState.Disconnecting(ActionAfterDisconnect.Block), - inAddress = null, - outAddress = "", - showLocation = true, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = InAppNotification.TunnelStateBlocked, - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val mockRelayLocation: RelayItem = mockk(relaxed = true) + val mockLocationName = "Home" + every { mockRelayLocation.locationName } returns mockLocationName + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = mockRelayLocation, + tunnelUiState = TunnelState.Disconnecting(ActionAfterDisconnect.Block), + tunnelRealState = + TunnelState.Disconnecting(ActionAfterDisconnect.Block), + inAddress = null, + outAddress = "", + showLocation = true, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = InAppNotification.TunnelStateBlocked, + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("SECURE CONNECTION").assertExists() onNodeWithText(mockLocationName).assertExists() onNodeWithText("Disconnect").assertExists() @@ -409,199 +426,210 @@ class ConnectScreenTest { @Test fun testClickSelectLocationButton() { - // Arrange - val mockRelayLocation: RelayItem = mockk(relaxed = true) - val mockLocationName = "Home" - every { mockRelayLocation.locationName } returns mockLocationName - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = mockRelayLocation, - tunnelUiState = TunnelState.Disconnected(), - tunnelRealState = TunnelState.Disconnected(), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = null, - isPlayBuild = false - ), - onSwitchLocationClick = mockedClickHandler - ) - } + composeExtension.use { + // Arrange + val mockRelayLocation: RelayItem = mockk(relaxed = true) + val mockLocationName = "Home" + every { mockRelayLocation.locationName } returns mockLocationName + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = mockRelayLocation, + tunnelUiState = TunnelState.Disconnected(), + tunnelRealState = TunnelState.Disconnected(), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = null, + isPlayBuild = false + ), + onSwitchLocationClick = mockedClickHandler + ) + } - // Act - composeTestRule.onNodeWithTag(SELECT_LOCATION_BUTTON_TEST_TAG).performClick() + // Act + onNodeWithTag(SELECT_LOCATION_BUTTON_TEST_TAG).performClick() - // Assert - verify { mockedClickHandler.invoke() } + // Assert + verify { mockedClickHandler.invoke() } + } } @Test fun testOnDisconnectClick() { - // Arrange - val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connected(mockTunnelEndpoint, null), - tunnelRealState = TunnelState.Connected(mockTunnelEndpoint, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = null, - isPlayBuild = false - ), - onDisconnectClick = mockedClickHandler - ) - } + composeExtension.use { + // Arrange + val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Connected(mockTunnelEndpoint, null), + tunnelRealState = TunnelState.Connected(mockTunnelEndpoint, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = null, + isPlayBuild = false + ), + onDisconnectClick = mockedClickHandler + ) + } - // Act - composeTestRule.onNodeWithTag(CONNECT_BUTTON_TEST_TAG).performClick() + // Act + onNodeWithTag(CONNECT_BUTTON_TEST_TAG).performClick() - // Assert - verify { mockedClickHandler.invoke() } + // Assert + verify { mockedClickHandler.invoke() } + } } @Test fun testOnReconnectClick() { - // Arrange - val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connected(mockTunnelEndpoint, null), - tunnelRealState = TunnelState.Connected(mockTunnelEndpoint, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = null, - isPlayBuild = false - ), - onReconnectClick = mockedClickHandler - ) - } + composeExtension.use { + // Arrange + val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Connected(mockTunnelEndpoint, null), + tunnelRealState = TunnelState.Connected(mockTunnelEndpoint, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = null, + isPlayBuild = false + ), + onReconnectClick = mockedClickHandler + ) + } - // Act - composeTestRule.onNodeWithTag(RECONNECT_BUTTON_TEST_TAG).performClick() + // Act + onNodeWithTag(RECONNECT_BUTTON_TEST_TAG).performClick() - // Assert - verify { mockedClickHandler.invoke() } + // Assert + verify { mockedClickHandler.invoke() } + } } @Test fun testOnConnectClick() { - // Arrange - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Disconnected(), - tunnelRealState = TunnelState.Disconnected(), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = null, - isPlayBuild = false - ), - onConnectClick = mockedClickHandler - ) - } + composeExtension.use { + // Arrange + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Disconnected(), + tunnelRealState = TunnelState.Disconnected(), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = null, + isPlayBuild = false + ), + onConnectClick = mockedClickHandler + ) + } - // Act - composeTestRule.onNodeWithTag(CONNECT_BUTTON_TEST_TAG).performClick() + // Act + onNodeWithTag(CONNECT_BUTTON_TEST_TAG).performClick() - // Assert - verify { mockedClickHandler.invoke() } + // Assert + verify { mockedClickHandler.invoke() } + } } @Test fun testOnCancelClick() { - // Arrange - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connecting(null, null), - tunnelRealState = TunnelState.Connecting(null, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = null, - isPlayBuild = false - ), - onCancelClick = mockedClickHandler - ) - } + composeExtension.use { + // Arrange + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Connecting(null, null), + tunnelRealState = TunnelState.Connecting(null, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = null, + isPlayBuild = false + ), + onCancelClick = mockedClickHandler + ) + } - // Act - composeTestRule.onNodeWithTag(CONNECT_BUTTON_TEST_TAG).performClick() + // Act + onNodeWithTag(CONNECT_BUTTON_TEST_TAG).performClick() - // Assert - verify { mockedClickHandler.invoke() } + // Assert + verify { mockedClickHandler.invoke() } + } } @Test fun showLocationInfo() { - // Arrange - val mockLocation: GeoIpLocation = mockk(relaxed = true) - val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) - val mockHostName = "Host-Name" - val mockPort = 99 - val mockHost = "Host" - val mockProtocol = TransportProtocol.Udp - val mockInAddress = Triple(mockHost, mockPort, mockProtocol) - val mockOutAddress = "HostAddressV4 / HostAddressV4" - every { mockLocation.hostname } returns mockHostName - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = mockLocation, - relayLocation = null, - tunnelUiState = TunnelState.Connected(mockTunnelEndpoint, null), - tunnelRealState = TunnelState.Connected(mockTunnelEndpoint, null), - inAddress = mockInAddress, - outAddress = mockOutAddress, - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = null, - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val mockLocation: GeoIpLocation = mockk(relaxed = true) + val mockTunnelEndpoint: TunnelEndpoint = mockk(relaxed = true) + val mockHostName = "Host-Name" + val mockPort = 99 + val mockHost = "Host" + val mockProtocol = TransportProtocol.Udp + val mockInAddress = Triple(mockHost, mockPort, mockProtocol) + val mockOutAddress = "HostAddressV4 / HostAddressV4" + every { mockLocation.hostname } returns mockHostName + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = mockLocation, + relayLocation = null, + tunnelUiState = TunnelState.Connected(mockTunnelEndpoint, null), + tunnelRealState = TunnelState.Connected(mockTunnelEndpoint, null), + inAddress = mockInAddress, + outAddress = mockOutAddress, + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = null, + isPlayBuild = false + ), + ) + } - composeTestRule.onNodeWithTag(LOCATION_INFO_TEST_TAG).performClick() + // Act + onNodeWithTag(LOCATION_INFO_TEST_TAG).performClick() - // Assert - composeTestRule.apply { + // Assert onNodeWithText(mockHostName).assertExists() onNodeWithText("WireGuard").assertExists() onNodeWithText("In $mockHost:$mockPort UDP").assertExists() @@ -611,35 +639,35 @@ class ConnectScreenTest { @Test fun testOutdatedVersionNotification() { - // Arrange - val versionInfo = - VersionInfo( - currentVersion = "1.0", - upgradeVersion = "1.1", - isOutdated = true, - isSupported = true - ) - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connecting(null, null), - tunnelRealState = TunnelState.Connecting(null, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = InAppNotification.UpdateAvailable(versionInfo), - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val versionInfo = + VersionInfo( + currentVersion = "1.0", + upgradeVersion = "1.1", + isOutdated = true, + isSupported = true + ) + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Connecting(null, null), + tunnelRealState = TunnelState.Connecting(null, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = InAppNotification.UpdateAvailable(versionInfo), + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("UPDATE AVAILABLE").assertExists() onNodeWithText("Install Mullvad VPN (1.1) to stay up to date").assertExists() } @@ -647,35 +675,35 @@ class ConnectScreenTest { @Test fun testUnsupportedVersionNotification() { - // Arrange - val versionInfo = - VersionInfo( - currentVersion = "1.0", - upgradeVersion = "1.1", - isOutdated = true, - isSupported = false - ) - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connecting(null, null), - tunnelRealState = TunnelState.Connecting(null, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = InAppNotification.UnsupportedVersion(versionInfo), - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val versionInfo = + VersionInfo( + currentVersion = "1.0", + upgradeVersion = "1.1", + isOutdated = true, + isSupported = false + ) + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Connecting(null, null), + tunnelRealState = TunnelState.Connecting(null, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = InAppNotification.UnsupportedVersion(versionInfo), + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("UNSUPPORTED VERSION").assertExists() onNodeWithText( "Your privacy might be at risk with this unsupported app version. Please update now." @@ -686,29 +714,29 @@ class ConnectScreenTest { @Test fun testAccountExpiredNotification() { - // Arrange - val expiryDate = DateTime(2020, 11, 11, 10, 10) - composeTestRule.setContentWithTheme { - ConnectScreen( - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connecting(null, null), - tunnelRealState = TunnelState.Connecting(null, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = InAppNotification.AccountExpiry(expiryDate), - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val expiryDate = DateTime(2020, 11, 11, 10, 10) + setContentWithTheme { + ConnectScreen( + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Connecting(null, null), + tunnelRealState = TunnelState.Connecting(null, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = InAppNotification.AccountExpiry(expiryDate), + isPlayBuild = false + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("ACCOUNT CREDIT EXPIRES SOON").assertExists() onNodeWithText("Out of time").assertExists() } @@ -716,85 +744,94 @@ class ConnectScreenTest { @Test fun testOnUpdateVersionClick() { - // Arrange - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - val versionInfo = - VersionInfo( - currentVersion = "1.0", - upgradeVersion = "1.1", - isOutdated = true, - isSupported = false - ) - composeTestRule.setContentWithTheme { - ConnectScreen( - onUpdateVersionClick = mockedClickHandler, - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connecting(null, null), - tunnelRealState = TunnelState.Connecting(null, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = InAppNotification.UnsupportedVersion(versionInfo), - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + val versionInfo = + VersionInfo( + currentVersion = "1.0", + upgradeVersion = "1.1", + isOutdated = true, + isSupported = false + ) + setContentWithTheme { + ConnectScreen( + onUpdateVersionClick = mockedClickHandler, + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Connecting(null, null), + tunnelRealState = TunnelState.Connecting(null, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = InAppNotification.UnsupportedVersion(versionInfo), + isPlayBuild = false + ), + ) + } - // Act - composeTestRule.onNodeWithTag(NOTIFICATION_BANNER_ACTION).performClick() + // Act + onNodeWithTag(NOTIFICATION_BANNER_ACTION).performClick() - // Assert - verify { mockedClickHandler.invoke() } + // Assert + verify { mockedClickHandler.invoke() } + } } @Test fun testOnShowAccountClick() { - // Arrange - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - val expiryDate = DateTime(2020, 11, 11, 10, 10) - composeTestRule.setContentWithTheme { - ConnectScreen( - onManageAccountClick = mockedClickHandler, - uiState = - ConnectUiState( - location = null, - relayLocation = null, - tunnelUiState = TunnelState.Connecting(null, null), - tunnelRealState = TunnelState.Connecting(null, null), - inAddress = null, - outAddress = "", - showLocation = false, - deviceName = "", - daysLeftUntilExpiry = null, - inAppNotification = InAppNotification.AccountExpiry(expiryDate), - isPlayBuild = false - ), - ) - } + composeExtension.use { + // Arrange + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + val expiryDate = DateTime(2020, 11, 11, 10, 10) + setContentWithTheme { + ConnectScreen( + onManageAccountClick = mockedClickHandler, + uiState = + ConnectUiState( + location = null, + relayLocation = null, + tunnelUiState = TunnelState.Connecting(null, null), + tunnelRealState = TunnelState.Connecting(null, null), + inAddress = null, + outAddress = "", + showLocation = false, + deviceName = "", + daysLeftUntilExpiry = null, + inAppNotification = InAppNotification.AccountExpiry(expiryDate), + isPlayBuild = false + ), + ) + } - // Act - composeTestRule.onNodeWithTag(NOTIFICATION_BANNER_ACTION).performClick() + // Act + onNodeWithTag(NOTIFICATION_BANNER_ACTION).performClick() - // Assert - verify { mockedClickHandler.invoke() } + // Assert + verify { mockedClickHandler.invoke() } + } } @Test fun testOpenAccountView() { - // Arrange - val onAccountClickMockk: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - ConnectScreen(uiState = ConnectUiState.INITIAL, onAccountClick = onAccountClickMockk) - } + composeExtension.use { + // Arrange + val onAccountClickMockk: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + ConnectScreen( + uiState = ConnectUiState.INITIAL, + onAccountClick = onAccountClickMockk + ) + } - // Assert - composeTestRule.onNodeWithTag(TOP_BAR_ACCOUNT_BUTTON).performClick() + // Assert + onNodeWithTag(TOP_BAR_ACCOUNT_BUTTON).performClick() - verify(exactly = 1) { onAccountClickMockk() } + verify(exactly = 1) { onAccountClickMockk() } + } } } diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreenTest.kt index 56e24b9a08b8..f65a23fa9c48 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreenTest.kt @@ -1,64 +1,69 @@ package net.mullvad.mullvadvpn.compose.screen -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import io.mockk.mockk import io.mockk.verify import net.mullvad.mullvadvpn.compose.setContentWithTheme import net.mullvad.mullvadvpn.compose.state.DeviceRevokedUiState -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +@OptIn(ExperimentalTestApi::class) class DeviceRevokedScreenTest { - @get:Rule val composeTestRule = createComposeRule() + @JvmField @RegisterExtension val composeExtension = createComposeExtension() - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) } @Test - fun testUnblockWarningShowingWhenSecured() { - // Arrange - val state = DeviceRevokedUiState.SECURED + fun testUnblockWarningShowingWhenSecured() = + composeExtension.use { + // Arrange + val state = DeviceRevokedUiState.SECURED - // Act - composeTestRule.setContentWithTheme { DeviceRevokedScreen(state) } + // Act + setContentWithTheme { DeviceRevokedScreen(state) } - // Assert - composeTestRule.onNodeWithText(UNBLOCK_WARNING).assertExists() - } + // Assert + onNodeWithText(UNBLOCK_WARNING).assertExists() + } @Test - fun testUnblockWarningNotShowingWhenNotSecured() { - // Arrange - val state = DeviceRevokedUiState.UNSECURED + fun testUnblockWarningNotShowingWhenNotSecured() = + composeExtension.use { + // Arrange + val state = DeviceRevokedUiState.UNSECURED - // Act - composeTestRule.setContentWithTheme { DeviceRevokedScreen(state) } + // Act + setContentWithTheme { DeviceRevokedScreen(state) } - // Assert - composeTestRule.onNodeWithText(UNBLOCK_WARNING).assertDoesNotExist() - } + // Assert + onNodeWithText(UNBLOCK_WARNING).assertDoesNotExist() + } @Test - fun testGoToLogin() { - // Arrange - val state = DeviceRevokedUiState.UNSECURED - val mockOnGoToLoginClicked: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - DeviceRevokedScreen(state = state, onGoToLoginClicked = mockOnGoToLoginClicked) - } + fun testGoToLogin() = + composeExtension.use { + // Arrange + val state = DeviceRevokedUiState.UNSECURED + val mockOnGoToLoginClicked: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + DeviceRevokedScreen(state = state, onGoToLoginClicked = mockOnGoToLoginClicked) + } - // Act - composeTestRule.onNodeWithText(GO_TO_LOGIN_BUTTON_TEXT).performClick() + // Act + onNodeWithText(GO_TO_LOGIN_BUTTON_TEXT).performClick() - // Assert - verify { mockOnGoToLoginClicked.invoke() } - } + // Assert + verify { mockOnGoToLoginClicked.invoke() } + } companion object { private const val GO_TO_LOGIN_BUTTON_TEXT = "Go to login" diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreenTest.kt index 32fd727329fc..11f15b7823ce 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreenTest.kt @@ -1,8 +1,9 @@ package net.mullvad.mullvadvpn.compose.screen -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import io.mockk.mockk import io.mockk.verify @@ -10,132 +11,128 @@ import net.mullvad.mullvadvpn.compose.setContentWithTheme import net.mullvad.mullvadvpn.compose.state.RelayFilterState import net.mullvad.mullvadvpn.model.Ownership import net.mullvad.mullvadvpn.relaylist.Provider -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +@OptIn(ExperimentalTestApi::class) class FilterScreenTest { - @get:Rule val composeTestRule = createComposeRule() + @JvmField @RegisterExtension val composeExtension = createComposeExtension() fun setup() { MockKAnnotations.init(this) } @Test - fun testDefaultState() { - composeTestRule.setContentWithTheme { - FilterScreen( - uiState = - RelayFilterState( - allProviders = DUMMY_RELAY_ALL_PROVIDERS, - selectedOwnership = null, - selectedProviders = DUMMY_SELECTED_PROVIDERS, - ), - onSelectedProvider = { _, _ -> } - ) - } - composeTestRule.apply { + fun testDefaultState() = + composeExtension.use { + setContentWithTheme { + FilterScreen( + uiState = + RelayFilterState( + allProviders = DUMMY_RELAY_ALL_PROVIDERS, + selectedOwnership = null, + selectedProviders = DUMMY_SELECTED_PROVIDERS, + ), + onSelectedProvider = { _, _ -> } + ) + } onNodeWithText("Ownership").assertExists() onNodeWithText("Providers").assertExists() } - } @Test - fun testIsAnyCellShowing() { - composeTestRule.setContentWithTheme { - FilterScreen( - uiState = - RelayFilterState( - allProviders = DUMMY_RELAY_ALL_PROVIDERS, - selectedOwnership = null, - selectedProviders = DUMMY_SELECTED_PROVIDERS - ), - onSelectedProvider = { _, _ -> } - ) - } - composeTestRule.apply { + fun testIsAnyCellShowing() = + composeExtension.use { + setContentWithTheme { + FilterScreen( + uiState = + RelayFilterState( + allProviders = DUMMY_RELAY_ALL_PROVIDERS, + selectedOwnership = null, + selectedProviders = DUMMY_SELECTED_PROVIDERS + ), + onSelectedProvider = { _, _ -> } + ) + } onNodeWithText("Ownership").performClick() onNodeWithText("Any").assertExists() } - } @Test - fun testIsMullvadCellShowing() { - composeTestRule.setContentWithTheme { - FilterScreen( - uiState = - RelayFilterState( - allProviders = DUMMY_RELAY_ALL_PROVIDERS, - selectedOwnership = Ownership.MullvadOwned, - selectedProviders = DUMMY_SELECTED_PROVIDERS - ), - onSelectedProvider = { _, _ -> } - ) - } - composeTestRule.apply { + fun testIsMullvadCellShowing() = + composeExtension.use { + setContentWithTheme { + FilterScreen( + uiState = + RelayFilterState( + allProviders = DUMMY_RELAY_ALL_PROVIDERS, + selectedOwnership = Ownership.MullvadOwned, + selectedProviders = DUMMY_SELECTED_PROVIDERS + ), + onSelectedProvider = { _, _ -> } + ) + } onNodeWithText("Ownership").performClick() onNodeWithText("Mullvad owned only").assertExists() } - } @Test - fun testIsRentedCellShowing() { - composeTestRule.setContentWithTheme { - FilterScreen( - uiState = - RelayFilterState( - allProviders = DUMMY_RELAY_ALL_PROVIDERS, - selectedOwnership = Ownership.Rented, - selectedProviders = DUMMY_SELECTED_PROVIDERS - ), - onSelectedProvider = { _, _ -> } - ) - } - composeTestRule.apply { + fun testIsRentedCellShowing() = + composeExtension.use { + setContentWithTheme { + FilterScreen( + uiState = + RelayFilterState( + allProviders = DUMMY_RELAY_ALL_PROVIDERS, + selectedOwnership = Ownership.Rented, + selectedProviders = DUMMY_SELECTED_PROVIDERS + ), + onSelectedProvider = { _, _ -> } + ) + } onNodeWithText("Ownership").performClick() onNodeWithText("Rented only").assertExists() } - } @Test - fun testShowProviders() { - composeTestRule.setContentWithTheme { - FilterScreen( - uiState = - RelayFilterState( - allProviders = DUMMY_RELAY_ALL_PROVIDERS, - selectedOwnership = null, - selectedProviders = DUMMY_SELECTED_PROVIDERS - ), - onSelectedProvider = { _, _ -> } - ) - } + fun testShowProviders() = + composeExtension.use { + setContentWithTheme { + FilterScreen( + uiState = + RelayFilterState( + allProviders = DUMMY_RELAY_ALL_PROVIDERS, + selectedOwnership = null, + selectedProviders = DUMMY_SELECTED_PROVIDERS + ), + onSelectedProvider = { _, _ -> } + ) + } - composeTestRule.apply { onNodeWithText("Providers").performClick() onNodeWithText("Creanova").assertExists() - onNodeWithText("Creanova").assertExists() onNodeWithText("100TB").assertExists() } - } @Test - fun testApplyButtonClick() { - val mockClickListener: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - FilterScreen( - uiState = - RelayFilterState( - allProviders = listOf(), - selectedOwnership = null, - selectedProviders = listOf(Provider("31173", true)) - ), - onSelectedProvider = { _, _ -> }, - onApplyClick = mockClickListener - ) + fun testApplyButtonClick() = + composeExtension.use { + val mockClickListener: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + FilterScreen( + uiState = + RelayFilterState( + allProviders = listOf(), + selectedOwnership = null, + selectedProviders = listOf(Provider("31173", true)) + ), + onSelectedProvider = { _, _ -> }, + onApplyClick = mockClickListener + ) + } + onNodeWithText("Apply").performClick() + verify { mockClickListener() } } - composeTestRule.onNodeWithText("Apply").performClick() - verify { mockClickListener() } - } companion object { diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreenTest.kt index d43a0931a1d6..f3f1d0cba51e 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreenTest.kt @@ -1,9 +1,10 @@ package net.mullvad.mullvadvpn.compose.screen -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk @@ -17,34 +18,35 @@ import net.mullvad.mullvadvpn.lib.payment.model.PaymentStatus import net.mullvad.mullvadvpn.lib.payment.model.ProductId import net.mullvad.mullvadvpn.lib.payment.model.ProductPrice import net.mullvad.mullvadvpn.model.TunnelState -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +@OptIn(ExperimentalTestApi::class) class OutOfTimeScreenTest { - @get:Rule val composeTestRule = createComposeRule() + @JvmField @RegisterExtension val composeExtension = createComposeExtension() - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) } @Test - fun testDisableSitePayment() { - // Arrange - composeTestRule.setContentWithTheme { - OutOfTimeScreen( - uiState = OutOfTimeUiState(deviceName = ""), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onDisconnectClick = {} - ) - } + fun testDisableSitePayment() = + composeExtension.use { + // Arrange + setContentWithTheme { + OutOfTimeScreen( + uiState = OutOfTimeUiState(deviceName = ""), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onDisconnectClick = {} + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText( "Either buy credit on our website or redeem a voucher.", substring = true @@ -52,241 +54,251 @@ class OutOfTimeScreenTest { .assertDoesNotExist() onNodeWithText("Buy credit").assertDoesNotExist() } - } @Test - fun testOpenAccountView() { - // Arrange - composeTestRule.setContentWithTheme { - OutOfTimeScreen( - uiState = OutOfTimeUiState(deviceName = "", showSitePayment = true), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onDisconnectClick = {} - ) - } + fun testOpenAccountView() = + composeExtension.use { + // Arrange + setContentWithTheme { + OutOfTimeScreen( + uiState = OutOfTimeUiState(deviceName = "", showSitePayment = true), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onDisconnectClick = {} + ) + } - // Assert - composeTestRule.apply { onNodeWithText("Congrats!").assertDoesNotExist() } - } + // Assert + onNodeWithText("Congrats!").assertDoesNotExist() + } @Test - fun testClickSitePaymentButton() { - // Arrange - val mockClickListener: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - OutOfTimeScreen( - uiState = OutOfTimeUiState(deviceName = "", showSitePayment = true), - onSitePaymentClick = mockClickListener, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onDisconnectClick = {} - ) - } + fun testClickSitePaymentButton() = + composeExtension.use { + // Arrange + val mockClickListener: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + OutOfTimeScreen( + uiState = OutOfTimeUiState(deviceName = "", showSitePayment = true), + onSitePaymentClick = mockClickListener, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onDisconnectClick = {} + ) + } - // Act - composeTestRule.apply { onNodeWithText("Buy credit").performClick() } + // Act + onNodeWithText("Buy credit").performClick() - // Assert - verify(exactly = 1) { mockClickListener.invoke() } - } + // Assert + verify(exactly = 1) { mockClickListener.invoke() } + } @Test - fun testClickRedeemVoucher() { - // Arrange - val mockClickListener: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - OutOfTimeScreen( - uiState = OutOfTimeUiState(deviceName = "", showSitePayment = true), - onSitePaymentClick = {}, - onRedeemVoucherClick = mockClickListener, - onSettingsClick = {}, - onAccountClick = {}, - onDisconnectClick = {} - ) - } + fun testClickRedeemVoucher() = + composeExtension.use { + // Arrange + val mockClickListener: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + OutOfTimeScreen( + uiState = OutOfTimeUiState(deviceName = "", showSitePayment = true), + onSitePaymentClick = {}, + onRedeemVoucherClick = mockClickListener, + onSettingsClick = {}, + onAccountClick = {}, + onDisconnectClick = {} + ) + } - // Act - composeTestRule.apply { onNodeWithText("Redeem voucher").performClick() } + // Act + onNodeWithText("Redeem voucher").performClick() - // Assert - verify(exactly = 1) { mockClickListener.invoke() } - } + // Assert + verify(exactly = 1) { mockClickListener.invoke() } + } @Test - fun testClickDisconnect() { - // Arrange - val mockClickListener: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - OutOfTimeScreen( - uiState = - OutOfTimeUiState( - tunnelState = TunnelState.Connecting(null, null), - deviceName = "", - showSitePayment = true - ), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onDisconnectClick = mockClickListener - ) - } + fun testClickDisconnect() = + composeExtension.use { + // Arrange + val mockClickListener: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + OutOfTimeScreen( + uiState = + OutOfTimeUiState( + tunnelState = TunnelState.Connecting(null, null), + deviceName = "", + showSitePayment = true + ), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onDisconnectClick = mockClickListener + ) + } - // Act - composeTestRule.apply { onNodeWithText("Disconnect").performClick() } + // Act + onNodeWithText("Disconnect").performClick() - // Assert - verify(exactly = 1) { mockClickListener.invoke() } - } + // Assert + verify(exactly = 1) { mockClickListener.invoke() } + } @Test - fun testShowBillingErrorPaymentButton() { - // Arrange - composeTestRule.setContentWithTheme { - OutOfTimeScreen( - uiState = - OutOfTimeUiState( - showSitePayment = true, - billingPaymentState = PaymentState.Error.Billing - ), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = { _ -> } - ) - } + fun testShowBillingErrorPaymentButton() = + composeExtension.use { + // Arrange + setContentWithTheme { + OutOfTimeScreen( + uiState = + OutOfTimeUiState( + showSitePayment = true, + billingPaymentState = PaymentState.Error.Billing + ), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> } + ) + } - // Assert - composeTestRule.onNodeWithText("Add 30 days time").assertExists() - } + // Assert + onNodeWithText("Add 30 days time").assertExists() + } @Test - fun testShowBillingPaymentAvailable() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns null - composeTestRule.setContentWithTheme { - OutOfTimeScreen( - uiState = - OutOfTimeUiState( - showSitePayment = true, - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) - ), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = { _ -> } - ) - } + fun testShowBillingPaymentAvailable() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns null + setContentWithTheme { + OutOfTimeScreen( + uiState = + OutOfTimeUiState( + showSitePayment = true, + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) + ), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> } + ) + } - // Assert - composeTestRule.onNodeWithText("Add 30 days time ($10)").assertExists() - } + // Assert + onNodeWithText("Add 30 days time ($10)").assertExists() + } @Test - fun testShowPendingPayment() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns PaymentStatus.PENDING - composeTestRule.setContentWithTheme { - OutOfTimeScreen( - uiState = - OutOfTimeUiState( - showSitePayment = true, - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) - ), - ) - } + fun testShowPendingPayment() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns PaymentStatus.PENDING + setContentWithTheme { + OutOfTimeScreen( + uiState = + OutOfTimeUiState( + showSitePayment = true, + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) + ), + ) + } - // Assert - composeTestRule.onNodeWithText("Google Play payment pending").assertExists() - } + // Assert + onNodeWithText("Google Play payment pending").assertExists() + } @Test - fun testShowPendingPaymentInfoDialog() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns PaymentStatus.PENDING - val mockNavigateToVerificationPending: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - OutOfTimeScreen( - uiState = - OutOfTimeUiState( - showSitePayment = true, - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) - ), - navigateToVerificationPendingDialog = mockNavigateToVerificationPending - ) - } + fun testShowPendingPaymentInfoDialog() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns PaymentStatus.PENDING + val mockNavigateToVerificationPending: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + OutOfTimeScreen( + uiState = + OutOfTimeUiState( + showSitePayment = true, + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) + ), + navigateToVerificationPendingDialog = mockNavigateToVerificationPending + ) + } - // Act - composeTestRule.onNodeWithTag(PLAY_PAYMENT_INFO_ICON_TEST_TAG).performClick() - composeTestRule.onNodeWithTag(PLAY_PAYMENT_INFO_ICON_TEST_TAG).assertExists() + // Act + onNodeWithTag(PLAY_PAYMENT_INFO_ICON_TEST_TAG).performClick() + onNodeWithTag(PLAY_PAYMENT_INFO_ICON_TEST_TAG).assertExists() - verify(exactly = 1) { mockNavigateToVerificationPending.invoke() } - } + // Assert + verify(exactly = 1) { mockNavigateToVerificationPending.invoke() } + } @Test - fun testShowVerificationInProgress() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns PaymentStatus.VERIFICATION_IN_PROGRESS - composeTestRule.setContentWithTheme { - OutOfTimeScreen( - uiState = - OutOfTimeUiState( - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)), - showSitePayment = true, - ) - ) - } + fun testShowVerificationInProgress() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns PaymentStatus.VERIFICATION_IN_PROGRESS + setContentWithTheme { + OutOfTimeScreen( + uiState = + OutOfTimeUiState( + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)), + showSitePayment = true, + ) + ) + } - // Assert - composeTestRule.onNodeWithText("Verifying purchase").assertExists() - } + // Assert + onNodeWithText("Verifying purchase").assertExists() + } @Test - fun testOnPurchaseBillingProductClick() { - // Arrange - val clickHandler: (ProductId) -> Unit = mockk(relaxed = true) - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.productId } returns ProductId("PRODUCT_ID") - every { mockPaymentProduct.status } returns null - composeTestRule.setContentWithTheme { - OutOfTimeScreen( - uiState = - OutOfTimeUiState( - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)), - showSitePayment = true, - ), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = clickHandler - ) - } + fun testOnPurchaseBillingProductClick() = + composeExtension.use { + // Arrange + val clickHandler: (ProductId) -> Unit = mockk(relaxed = true) + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.productId } returns ProductId("PRODUCT_ID") + every { mockPaymentProduct.status } returns null + setContentWithTheme { + OutOfTimeScreen( + uiState = + OutOfTimeUiState( + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)), + showSitePayment = true, + ), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = clickHandler + ) + } - // Act - composeTestRule.onNodeWithText("Add 30 days time ($10)").performClick() + // Act + onNodeWithText("Add 30 days time ($10)").performClick() - // Assert - verify { clickHandler(ProductId("PRODUCT_ID")) } - } + // Assert + verify { clickHandler(ProductId("PRODUCT_ID")) } + } } diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogTest.kt index 5a51a8f88538..9a2a9ce86ddf 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogTest.kt @@ -1,10 +1,11 @@ package net.mullvad.mullvadvpn.compose.screen -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi 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 de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.mockk import io.mockk.mockkObject import io.mockk.verify @@ -14,132 +15,139 @@ 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.util.VoucherRegexHelper -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +@OptIn(ExperimentalTestApi::class) class RedeemVoucherDialogTest { - @get:Rule val composeTestRule = createComposeRule() + @JvmField @RegisterExtension val composeExtension = createComposeExtension() - @Before + @BeforeEach fun setup() { mockkObject(VoucherRegexHelper) } @Test - fun testDismissDialog() { - // Arrange - val mockedClickHandler: (Boolean) -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - RedeemVoucherDialog( - uiState = VoucherDialogUiState.INITIAL, - onVoucherInputChange = {}, - onRedeem = {}, - onDismiss = mockedClickHandler - ) + fun testDismissDialog() = + composeExtension.use { + // Arrange + val mockedClickHandler: (Boolean) -> Unit = mockk(relaxed = true) + setContentWithTheme { + RedeemVoucherDialog( + uiState = VoucherDialogUiState.INITIAL, + onVoucherInputChange = {}, + onRedeem = {}, + onDismiss = mockedClickHandler + ) + } + + // Act + onNodeWithText(CANCEL_BUTTON_TEXT).performClick() + + // Assert + verify { mockedClickHandler.invoke(false) } } - // Act - composeTestRule.onNodeWithText(CANCEL_BUTTON_TEXT).performClick() - - // Assert - verify { mockedClickHandler.invoke(false) } - } - @Test - fun testDismissDialogAfterSuccessfulRedeem() { - // Arrange - val mockedClickHandler: (Boolean) -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - RedeemVoucherDialog( - uiState = - VoucherDialogUiState(voucherViewModelState = VoucherDialogState.Success(0)), - onVoucherInputChange = {}, - onRedeem = {}, - onDismiss = mockedClickHandler - ) + fun testDismissDialogAfterSuccessfulRedeem() = + composeExtension.use { + // Arrange + val mockedClickHandler: (Boolean) -> Unit = mockk(relaxed = true) + setContentWithTheme { + RedeemVoucherDialog( + uiState = + VoucherDialogUiState(voucherViewModelState = VoucherDialogState.Success(0)), + onVoucherInputChange = {}, + onRedeem = {}, + onDismiss = mockedClickHandler + ) + } + + // Act + onNodeWithText(GOT_IT_BUTTON_TEXT).performClick() + + // Assert + verify { mockedClickHandler.invoke(true) } } - // Act - composeTestRule.onNodeWithText(GOT_IT_BUTTON_TEXT).performClick() - - // Assert - verify { mockedClickHandler.invoke(true) } - } - @Test - fun testInsertVoucher() { - // Arrange - val mockedClickHandler: (String) -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - RedeemVoucherDialog( - uiState = VoucherDialogUiState(), - onVoucherInputChange = mockedClickHandler, - onRedeem = {}, - onDismiss = {} - ) + fun testInsertVoucher() = + composeExtension.use { + // Arrange + val mockedClickHandler: (String) -> Unit = mockk(relaxed = true) + setContentWithTheme { + RedeemVoucherDialog( + uiState = VoucherDialogUiState(), + onVoucherInputChange = mockedClickHandler, + onRedeem = {}, + onDismiss = {} + ) + } + + // Act + onNodeWithTag(VOUCHER_INPUT_TEST_TAG).performTextInput(DUMMY_VOUCHER) + + // Assert + verify { mockedClickHandler.invoke(DUMMY_VOUCHER) } } - // Act - composeTestRule.onNodeWithTag(VOUCHER_INPUT_TEST_TAG).performTextInput(DUMMY_VOUCHER) - - // Assert - verify { mockedClickHandler.invoke(DUMMY_VOUCHER) } - } - @Test - fun testVerifyingState() { - // Arrange - composeTestRule.setContentWithTheme { - RedeemVoucherDialog( - uiState = - VoucherDialogUiState(voucherViewModelState = VoucherDialogState.Verifying), - onVoucherInputChange = {}, - onRedeem = {}, - onDismiss = {} - ) + fun testVerifyingState() = + composeExtension.use { + // Arrange + setContentWithTheme { + RedeemVoucherDialog( + uiState = + VoucherDialogUiState(voucherViewModelState = VoucherDialogState.Verifying), + onVoucherInputChange = {}, + onRedeem = {}, + onDismiss = {} + ) + } + + // Assert + onNodeWithText("Verifying voucher…").assertExists() } - // Assert - composeTestRule.onNodeWithText("Verifying voucher…").assertExists() - } - @Test - fun testSuccessState() { - // Arrange - composeTestRule.setContentWithTheme { - RedeemVoucherDialog( - uiState = - VoucherDialogUiState(voucherViewModelState = VoucherDialogState.Success(0)), - onVoucherInputChange = {}, - onRedeem = {}, - onDismiss = {} - ) + fun testSuccessState() = + composeExtension.use { + // Arrange + setContentWithTheme { + RedeemVoucherDialog( + uiState = + VoucherDialogUiState(voucherViewModelState = VoucherDialogState.Success(0)), + onVoucherInputChange = {}, + onRedeem = {}, + onDismiss = {} + ) + } + + // Assert + onNodeWithText("Voucher was successfully redeemed.").assertExists() } - // Assert - composeTestRule.onNodeWithText("Voucher was successfully redeemed.").assertExists() - } - @Test - fun testErrorState() { - // Arrange - composeTestRule.setContentWithTheme { - RedeemVoucherDialog( - uiState = - VoucherDialogUiState( - voucherViewModelState = VoucherDialogState.Error(ERROR_MESSAGE) - ), - onVoucherInputChange = {}, - onRedeem = {}, - onDismiss = {} - ) + fun testErrorState() = + composeExtension.use { + // Arrange + setContentWithTheme { + RedeemVoucherDialog( + uiState = + VoucherDialogUiState( + voucherViewModelState = VoucherDialogState.Error(ERROR_MESSAGE) + ), + onVoucherInputChange = {}, + onRedeem = {}, + onDismiss = {} + ) + } + + // Assert + onNodeWithText(ERROR_MESSAGE).assertExists() } - // Assert - composeTestRule.onNodeWithText(ERROR_MESSAGE).assertExists() - } - companion object { private const val REDEEM_BUTTON_TEXT = "Redeem" private const val CANCEL_BUTTON_TEXT = "Cancel" diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt index ea1d26168949..fbc8b046fd77 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt @@ -1,9 +1,10 @@ package net.mullvad.mullvadvpn.compose.screen -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performTextInput +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import io.mockk.mockk import io.mockk.verify @@ -19,49 +20,51 @@ import net.mullvad.mullvadvpn.model.RelayListCountry import net.mullvad.mullvadvpn.model.WireguardEndpointData import net.mullvad.mullvadvpn.model.WireguardRelayEndpointData import net.mullvad.mullvadvpn.relaylist.toRelayCountries -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +@OptIn(ExperimentalTestApi::class) class SelectLocationScreenTest { - @get:Rule val composeTestRule = createComposeRule() + @JvmField @RegisterExtension val composeExtension = createComposeExtension() - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) } @Test - fun testDefaultState() { - // Arrange - composeTestRule.setContentWithTheme { - SelectLocationScreen( - uiState = SelectLocationUiState.Loading, - ) - } + fun testDefaultState() = + composeExtension.use { + // Arrange + setContentWithTheme { + SelectLocationScreen( + uiState = SelectLocationUiState.Loading, + ) + } - // Assert - composeTestRule.apply { onNodeWithTag(CIRCULAR_PROGRESS_INDICATOR).assertExists() } - } + // Assert + onNodeWithTag(CIRCULAR_PROGRESS_INDICATOR).assertExists() + } @Test - fun testShowRelayListState() { - // Arrange - composeTestRule.setContentWithTheme { - SelectLocationScreen( - uiState = - SelectLocationUiState.ShowData( - countries = DUMMY_RELAY_COUNTRIES, - selectedRelay = null, - selectedOwnership = null, - selectedProvidersCount = 0, - searchTerm = "" - ), - ) - } + fun testShowRelayListState() = + composeExtension.use { + // Arrange + setContentWithTheme { + SelectLocationScreen( + uiState = + SelectLocationUiState.ShowData( + countries = DUMMY_RELAY_COUNTRIES, + selectedRelay = null, + selectedOwnership = null, + selectedProvidersCount = 0, + searchTerm = "" + ), + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("Relay Country 1").assertExists() onNodeWithText("Relay City 1").assertDoesNotExist() onNodeWithText("Relay host 1").assertDoesNotExist() @@ -69,37 +72,36 @@ class SelectLocationScreenTest { onNodeWithText("Relay City 2").assertDoesNotExist() onNodeWithText("Relay host 2").assertDoesNotExist() } - } @Test - fun testShowRelayListStateSelected() { - val updatedDummyList = - DUMMY_RELAY_COUNTRIES.let { - val cities = it[0].cities.toMutableList() - val city = cities.removeAt(0) - cities.add(0, city.copy(expanded = true)) - - val mutableRelayList = it.toMutableList() - mutableRelayList[0] = it[0].copy(expanded = true, cities = cities.toList()) - mutableRelayList + fun testShowRelayListStateSelected() = + composeExtension.use { + val updatedDummyList = + DUMMY_RELAY_COUNTRIES.let { + val cities = it[0].cities.toMutableList() + val city = cities.removeAt(0) + cities.add(0, city.copy(expanded = true)) + + val mutableRelayList = it.toMutableList() + mutableRelayList[0] = it[0].copy(expanded = true, cities = cities.toList()) + mutableRelayList + } + + // Arrange + setContentWithTheme { + SelectLocationScreen( + uiState = + SelectLocationUiState.ShowData( + countries = updatedDummyList, + selectedRelay = updatedDummyList[0].cities[0].relays[0], + selectedOwnership = null, + selectedProvidersCount = 0, + searchTerm = "" + ), + ) } - // Arrange - composeTestRule.setContentWithTheme { - SelectLocationScreen( - uiState = - SelectLocationUiState.ShowData( - countries = updatedDummyList, - selectedRelay = updatedDummyList[0].cities[0].relays[0], - selectedOwnership = null, - selectedProvidersCount = 0, - searchTerm = "" - ), - ) - } - - // Assert - composeTestRule.apply { + // Assert onNodeWithText("Relay Country 1").assertExists() onNodeWithText("Relay City 1").assertExists() onNodeWithText("Relay host 1").assertExists() @@ -107,59 +109,58 @@ class SelectLocationScreenTest { onNodeWithText("Relay City 2").assertDoesNotExist() onNodeWithText("Relay host 2").assertDoesNotExist() } - } @Test - fun testSearchInput() { - // Arrange - val mockedSearchTermInput: (String) -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - SelectLocationScreen( - uiState = - SelectLocationUiState.ShowData( - countries = emptyList(), - selectedRelay = null, - selectedOwnership = null, - selectedProvidersCount = 0, - searchTerm = "" - ), - onSearchTermInput = mockedSearchTermInput - ) - } - val mockSearchString = "SEARCH" + fun testSearchInput() = + composeExtension.use { + // Arrange + val mockedSearchTermInput: (String) -> Unit = mockk(relaxed = true) + setContentWithTheme { + SelectLocationScreen( + uiState = + SelectLocationUiState.ShowData( + countries = emptyList(), + selectedRelay = null, + selectedOwnership = null, + selectedProvidersCount = 0, + searchTerm = "" + ), + onSearchTermInput = mockedSearchTermInput + ) + } + val mockSearchString = "SEARCH" - // Act - composeTestRule.apply { onNodeWithText("Search for...").performTextInput(mockSearchString) } + // Act + onNodeWithText("Search for...").performTextInput(mockSearchString) - // Assert - verify { mockedSearchTermInput.invoke(mockSearchString) } - } + // Assert + verify { mockedSearchTermInput.invoke(mockSearchString) } + } @Test - fun testSearchTermNotFound() { - // Arrange - val mockedSearchTermInput: (String) -> Unit = mockk(relaxed = true) - val mockSearchString = "SEARCH" - composeTestRule.setContentWithTheme { - SelectLocationScreen( - uiState = - SelectLocationUiState.ShowData( - countries = emptyList(), - selectedRelay = null, - selectedOwnership = null, - selectedProvidersCount = 0, - searchTerm = mockSearchString - ), - onSearchTermInput = mockedSearchTermInput - ) - } + fun testSearchTermNotFound() = + composeExtension.use { + // Arrange + val mockedSearchTermInput: (String) -> Unit = mockk(relaxed = true) + val mockSearchString = "SEARCH" + setContentWithTheme { + SelectLocationScreen( + uiState = + SelectLocationUiState.ShowData( + countries = emptyList(), + selectedRelay = null, + selectedOwnership = null, + selectedProvidersCount = 0, + searchTerm = mockSearchString + ), + onSearchTermInput = mockedSearchTermInput + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("No result for $mockSearchString.", substring = true).assertExists() onNodeWithText("Try a different search", substring = true).assertExists() } - } companion object { private val DUMMY_RELAY_1 = diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt index f5daf29bb2fa..1c47082b141a 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt @@ -1,66 +1,66 @@ package net.mullvad.mullvadvpn.compose.screen import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import net.mullvad.mullvadvpn.compose.setContentWithTheme import net.mullvad.mullvadvpn.compose.state.SettingsUiState -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +@OptIn(ExperimentalTestApi::class) class SettingsScreenTest { - @get:Rule val composeTestRule = createComposeRule() + @JvmField @RegisterExtension val composeExtension = createComposeExtension() - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) } @Test @OptIn(ExperimentalMaterial3Api::class) - fun testLoggedInState() { - // Arrange - composeTestRule.setContentWithTheme { - SettingsScreen( - uiState = - SettingsUiState( - appVersion = "", - isLoggedIn = true, - isUpdateAvailable = true, - isPlayBuild = false - ), - ) - } - // Assert - composeTestRule.apply { + fun testLoggedInState() = + composeExtension.use { + // Arrange + setContentWithTheme { + SettingsScreen( + uiState = + SettingsUiState( + appVersion = "", + isLoggedIn = true, + isUpdateAvailable = true, + isPlayBuild = false + ), + ) + } + // Assert onNodeWithText("VPN settings").assertExists() onNodeWithText("Split tunneling").assertExists() onNodeWithText("App version").assertExists() } - } @Test @OptIn(ExperimentalMaterial3Api::class) - fun testLoggedOutState() { - // Arrange - composeTestRule.setContentWithTheme { - SettingsScreen( - uiState = - SettingsUiState( - appVersion = "", - isLoggedIn = false, - isUpdateAvailable = true, - isPlayBuild = false - ), - ) - } - // Assert - composeTestRule.apply { + fun testLoggedOutState() = + composeExtension.use { + // Arrange + setContentWithTheme { + SettingsScreen( + uiState = + SettingsUiState( + appVersion = "", + isLoggedIn = false, + isUpdateAvailable = true, + isPlayBuild = false + ), + ) + } + // Assert onNodeWithText("VPN settings").assertDoesNotExist() onNodeWithText("Split tunneling").assertDoesNotExist() onNodeWithText("App version").assertExists() } - } } diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreenTest.kt index 7f4ee36d47bc..dc1362ad3700 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreenTest.kt @@ -1,9 +1,10 @@ package net.mullvad.mullvadvpn.compose.screen import android.graphics.Bitmap -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk @@ -14,15 +15,16 @@ import net.mullvad.mullvadvpn.applist.ApplicationsIconManager import net.mullvad.mullvadvpn.compose.setContentWithTheme import net.mullvad.mullvadvpn.compose.state.SplitTunnelingUiState import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension import org.koin.core.context.loadKoinModules import org.koin.core.context.unloadKoinModules import org.koin.dsl.module +@OptIn(ExperimentalTestApi::class) class SplitTunnelingScreenTest { - @get:Rule val composeTestRule = createComposeRule() + @JvmField @RegisterExtension val composeExtension = createComposeExtension() private val mockBitmap: Bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) private val testModule = module { @@ -33,7 +35,7 @@ class SplitTunnelingScreenTest { } } - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) loadKoinModules(testModule) @@ -46,42 +48,47 @@ class SplitTunnelingScreenTest { } @Test - fun testLoadingState() { - // Arrange - composeTestRule.setContentWithTheme { - SplitTunnelingScreen(uiState = SplitTunnelingUiState.Loading) - } + fun testLoadingState() = + composeExtension.use { + // Arrange + setContentWithTheme { SplitTunnelingScreen(uiState = SplitTunnelingUiState.Loading) } - // Assert - composeTestRule.apply { + // Assert onNodeWithText(TITLE).assertExists() onNodeWithText(DESCRIPTION).assertExists() onNodeWithText(EXCLUDED_APPLICATIONS).assertDoesNotExist() onNodeWithText(SHOW_SYSTEM_APPS).assertDoesNotExist() onNodeWithText(ALL_APPLICATIONS).assertDoesNotExist() } - } @Test - fun testListDisplayed() { - // Arrange - val excludedApp = - AppData(packageName = EXCLUDED_APP_PACKAGE_NAME, iconRes = 0, name = EXCLUDED_APP_NAME) - val includedApp = - AppData(packageName = INCLUDED_APP_PACKAGE_NAME, iconRes = 0, name = INCLUDED_APP_NAME) - composeTestRule.setContentWithTheme { - SplitTunnelingScreen( - uiState = - SplitTunnelingUiState.ShowAppList( - excludedApps = listOf(excludedApp), - includedApps = listOf(includedApp), - showSystemApps = false - ) - ) - } + fun testListDisplayed() = + composeExtension.use { + // Arrange + val excludedApp = + AppData( + packageName = EXCLUDED_APP_PACKAGE_NAME, + iconRes = 0, + name = EXCLUDED_APP_NAME + ) + val includedApp = + AppData( + packageName = INCLUDED_APP_PACKAGE_NAME, + iconRes = 0, + name = INCLUDED_APP_NAME + ) + setContentWithTheme { + SplitTunnelingScreen( + uiState = + SplitTunnelingUiState.ShowAppList( + excludedApps = listOf(excludedApp), + includedApps = listOf(includedApp), + showSystemApps = false + ) + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText(TITLE).assertExists() onNodeWithText(DESCRIPTION).assertExists() onNodeWithText(EXCLUDED_APPLICATIONS).assertExists() @@ -90,26 +97,29 @@ class SplitTunnelingScreenTest { onNodeWithText(ALL_APPLICATIONS).assertExists() onNodeWithText(INCLUDED_APP_NAME).assertExists() } - } @Test - fun testNoExcludedApps() { - // Arrange - val includedApp = - AppData(packageName = INCLUDED_APP_PACKAGE_NAME, iconRes = 0, name = INCLUDED_APP_NAME) - composeTestRule.setContentWithTheme { - SplitTunnelingScreen( - uiState = - SplitTunnelingUiState.ShowAppList( - excludedApps = emptyList(), - includedApps = listOf(includedApp), - showSystemApps = false - ) - ) - } + fun testNoExcludedApps() = + composeExtension.use { + // Arrange + val includedApp = + AppData( + packageName = INCLUDED_APP_PACKAGE_NAME, + iconRes = 0, + name = INCLUDED_APP_NAME + ) + setContentWithTheme { + SplitTunnelingScreen( + uiState = + SplitTunnelingUiState.ShowAppList( + excludedApps = emptyList(), + includedApps = listOf(includedApp), + showSystemApps = false + ) + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText(TITLE).assertExists() onNodeWithText(DESCRIPTION).assertExists() onNodeWithText(EXCLUDED_APPLICATIONS).assertDoesNotExist() @@ -118,88 +128,114 @@ class SplitTunnelingScreenTest { onNodeWithText(ALL_APPLICATIONS).assertExists() onNodeWithText(INCLUDED_APP_NAME).assertExists() } - } @Test - fun testClickIncludedItem() { - // Arrange - val excludedApp = - AppData(packageName = EXCLUDED_APP_PACKAGE_NAME, iconRes = 0, name = EXCLUDED_APP_NAME) - val includedApp = - AppData(packageName = INCLUDED_APP_PACKAGE_NAME, iconRes = 0, name = INCLUDED_APP_NAME) - val mockedClickHandler: (String) -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - SplitTunnelingScreen( - uiState = - SplitTunnelingUiState.ShowAppList( - excludedApps = listOf(excludedApp), - includedApps = listOf(includedApp), - showSystemApps = false - ), - onExcludeAppClick = mockedClickHandler - ) - } + fun testClickIncludedItem() = + composeExtension.use { + // Arrange + val excludedApp = + AppData( + packageName = EXCLUDED_APP_PACKAGE_NAME, + iconRes = 0, + name = EXCLUDED_APP_NAME + ) + val includedApp = + AppData( + packageName = INCLUDED_APP_PACKAGE_NAME, + iconRes = 0, + name = INCLUDED_APP_NAME + ) + val mockedClickHandler: (String) -> Unit = mockk(relaxed = true) + setContentWithTheme { + SplitTunnelingScreen( + uiState = + SplitTunnelingUiState.ShowAppList( + excludedApps = listOf(excludedApp), + includedApps = listOf(includedApp), + showSystemApps = false + ), + onExcludeAppClick = mockedClickHandler + ) + } - // Act - composeTestRule.onNodeWithText(INCLUDED_APP_NAME).performClick() + // Act + onNodeWithText(INCLUDED_APP_NAME).performClick() - // Assert - verify { mockedClickHandler.invoke(INCLUDED_APP_PACKAGE_NAME) } - } + // Assert + verify { mockedClickHandler.invoke(INCLUDED_APP_PACKAGE_NAME) } + } @Test - fun testClickExcludedItem() { - // Arrange - val excludedApp = - AppData(packageName = EXCLUDED_APP_PACKAGE_NAME, iconRes = 0, name = EXCLUDED_APP_NAME) - val includedApp = - AppData(packageName = INCLUDED_APP_PACKAGE_NAME, iconRes = 0, name = INCLUDED_APP_NAME) - val mockedClickHandler: (String) -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - SplitTunnelingScreen( - uiState = - SplitTunnelingUiState.ShowAppList( - excludedApps = listOf(excludedApp), - includedApps = listOf(includedApp), - showSystemApps = false - ), - onIncludeAppClick = mockedClickHandler - ) - } + fun testClickExcludedItem() = + composeExtension.use { + // Arrange + val excludedApp = + AppData( + packageName = EXCLUDED_APP_PACKAGE_NAME, + iconRes = 0, + name = EXCLUDED_APP_NAME + ) + val includedApp = + AppData( + packageName = INCLUDED_APP_PACKAGE_NAME, + iconRes = 0, + name = INCLUDED_APP_NAME + ) + val mockedClickHandler: (String) -> Unit = mockk(relaxed = true) + setContentWithTheme { + SplitTunnelingScreen( + uiState = + SplitTunnelingUiState.ShowAppList( + excludedApps = listOf(excludedApp), + includedApps = listOf(includedApp), + showSystemApps = false + ), + onIncludeAppClick = mockedClickHandler + ) + } - // Act - composeTestRule.onNodeWithText(EXCLUDED_APP_NAME).performClick() + // Act + onNodeWithText(EXCLUDED_APP_NAME).performClick() - // Assert - verify { mockedClickHandler.invoke(EXCLUDED_APP_PACKAGE_NAME) } - } + // Assert + verify { mockedClickHandler.invoke(EXCLUDED_APP_PACKAGE_NAME) } + } @Test - fun testClickShowSystemApps() { - // Arrange - val excludedApp = - AppData(packageName = EXCLUDED_APP_PACKAGE_NAME, iconRes = 0, name = EXCLUDED_APP_NAME) - val includedApp = - AppData(packageName = INCLUDED_APP_PACKAGE_NAME, iconRes = 0, name = INCLUDED_APP_NAME) - val mockedClickHandler: (Boolean) -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - SplitTunnelingScreen( - uiState = - SplitTunnelingUiState.ShowAppList( - excludedApps = listOf(excludedApp), - includedApps = listOf(includedApp), - showSystemApps = false - ), - onShowSystemAppsClick = mockedClickHandler - ) - } + fun testClickShowSystemApps() = + composeExtension.use { + // Arrange + val excludedApp = + AppData( + packageName = EXCLUDED_APP_PACKAGE_NAME, + iconRes = 0, + name = EXCLUDED_APP_NAME + ) + val includedApp = + AppData( + packageName = INCLUDED_APP_PACKAGE_NAME, + iconRes = 0, + name = INCLUDED_APP_NAME + ) + val mockedClickHandler: (Boolean) -> Unit = mockk(relaxed = true) + setContentWithTheme { + SplitTunnelingScreen( + uiState = + SplitTunnelingUiState.ShowAppList( + excludedApps = listOf(excludedApp), + includedApps = listOf(includedApp), + showSystemApps = false + ), + onShowSystemAppsClick = mockedClickHandler + ) + } - // Act - composeTestRule.onNodeWithText(SHOW_SYSTEM_APPS).performClick() + // Act + onNodeWithText(SHOW_SYSTEM_APPS).performClick() - // Assert - verify { mockedClickHandler.invoke(true) } - } + // Assert + verify { mockedClickHandler.invoke(true) } + } companion object { private const val EXCLUDED_APP_PACKAGE_NAME = "excluded-pkg" diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt index 1ca2b3e1f7b6..74c73553b2dc 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt @@ -1,12 +1,13 @@ package net.mullvad.mullvadvpn.compose.screen +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.hasTestTag -import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToNode +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import io.mockk.mockk import io.mockk.verify @@ -25,483 +26,483 @@ import net.mullvad.mullvadvpn.model.PortRange import net.mullvad.mullvadvpn.model.QuantumResistantState import net.mullvad.mullvadvpn.onNodeWithTagAndText import net.mullvad.mullvadvpn.viewmodel.CustomDnsItem -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +@OptIn(ExperimentalTestApi::class) class VpnSettingsScreenTest { - @get:Rule val composeTestRule = createComposeRule() + @JvmField @RegisterExtension val composeExtension = createComposeExtension() - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) } @Test - fun testDefaultState() { - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = VpnSettingsUiState.createDefault(), - ) + fun testDefaultState() = + composeExtension.use { + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = VpnSettingsUiState.createDefault(), + ) + } + + apply { onNodeWithText("Auto-connect").assertExists() } + + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG)) + + // Assert + apply { + onNodeWithText("WireGuard MTU").assertExists() + onNodeWithText("Default").assertExists() + } } - composeTestRule.apply { onNodeWithText("Auto-connect").assertExists() } - - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG)) - - // Assert - composeTestRule.apply { - onNodeWithText("WireGuard MTU").assertExists() - onNodeWithText("Default").assertExists() - } - } - @Test - fun testMtuCustomValue() { - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = VpnSettingsUiState.createDefault(mtu = VALID_DUMMY_MTU_VALUE), - ) + fun testMtuCustomValue() = + composeExtension.use { + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = VpnSettingsUiState.createDefault(mtu = VALID_DUMMY_MTU_VALUE), + ) + } + + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG)) + + // Assert + onNodeWithText(VALID_DUMMY_MTU_VALUE).assertExists() } - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG)) - - // Assert - composeTestRule.onNodeWithText(VALID_DUMMY_MTU_VALUE).assertExists() - } - @Test - fun testCustomDnsAddressesAndAddButtonVisibleWhenCustomDnsEnabled() { - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - isCustomDnsEnabled = true, - customDnsItems = - listOf( - CustomDnsItem(address = DUMMY_DNS_ADDRESS, false), - CustomDnsItem(address = DUMMY_DNS_ADDRESS_2, false), - CustomDnsItem(address = DUMMY_DNS_ADDRESS_3, false) - ) - ), - ) - } - - // Assert - composeTestRule.apply { + fun testCustomDnsAddressesAndAddButtonVisibleWhenCustomDnsEnabled() = + composeExtension.use { + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + isCustomDnsEnabled = true, + customDnsItems = + listOf( + CustomDnsItem(address = DUMMY_DNS_ADDRESS, false), + CustomDnsItem(address = DUMMY_DNS_ADDRESS_2, false), + CustomDnsItem(address = DUMMY_DNS_ADDRESS_3, false) + ) + ), + ) + } + + // Assert onNodeWithText(DUMMY_DNS_ADDRESS).assertExists() onNodeWithText(DUMMY_DNS_ADDRESS_2).assertExists() onNodeWithText(DUMMY_DNS_ADDRESS_3).assertExists() onNodeWithText("Add a server").assertExists() } - } @Test - fun testCustomDnsAddressesAndAddButtonNotVisibleWhenCustomDnsDisabled() { - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - isCustomDnsEnabled = false, - customDnsItems = listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, false)) - ), - ) + fun testCustomDnsAddressesAndAddButtonNotVisibleWhenCustomDnsDisabled() = + composeExtension.use { + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + isCustomDnsEnabled = false, + customDnsItems = + listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, false)) + ), + ) + } + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG)) + // Assert + onNodeWithText(DUMMY_DNS_ADDRESS).assertDoesNotExist() + onNodeWithText("Add a server").assertDoesNotExist() } - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG)) - // Assert - composeTestRule.onNodeWithText(DUMMY_DNS_ADDRESS).assertDoesNotExist() - composeTestRule.onNodeWithText("Add a server").assertDoesNotExist() - } @Test - fun testLanWarningNotShownWhenLanTrafficEnabledAndLocalAddressIsUsed() { - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - isCustomDnsEnabled = true, - isLocalNetworkSharingEnabled = true, - customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = true)) - ), - ) + fun testLanWarningNotShownWhenLanTrafficEnabledAndLocalAddressIsUsed() = + composeExtension.use { + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + isCustomDnsEnabled = true, + isLocalNetworkSharingEnabled = true, + customDnsItems = + listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = true)) + ), + ) + } + + // Assert + onNodeWithContentDescription(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() } - // Assert - composeTestRule.onNodeWithContentDescription(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() - } - @Test - fun testLanWarningNotShowedWhenLanTrafficDisabledAndLocalAddressIsNotUsed() { - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - isCustomDnsEnabled = true, - customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = false)) - ), - ) + fun testLanWarningNotShowedWhenLanTrafficDisabledAndLocalAddressIsNotUsed() = + composeExtension.use { + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + isCustomDnsEnabled = true, + customDnsItems = + listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = false)) + ), + ) + } + + // Assert + onNodeWithContentDescription(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() } - // Assert - composeTestRule.onNodeWithContentDescription(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() - } - @Test - fun testLanWarningNotShowedWhenLanTrafficEnabledAndLocalAddressIsNotUsed() { - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - isCustomDnsEnabled = true, - customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = false)) - ), - ) + fun testLanWarningNotShowedWhenLanTrafficEnabledAndLocalAddressIsNotUsed() = + composeExtension.use { + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + isCustomDnsEnabled = true, + customDnsItems = + listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = false)) + ), + ) + } + + // Assert + onNodeWithContentDescription(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() } - // Assert - composeTestRule.onNodeWithContentDescription(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() - } - @Test - fun testLanWarningShowedWhenAllowLanEnabledAndLocalDnsAddressIsUsed() { - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - isCustomDnsEnabled = true, - customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = true)) - ), - ) - } - - // Assert - composeTestRule.apply { + fun testLanWarningShowedWhenAllowLanEnabledAndLocalDnsAddressIsUsed() = + composeExtension.use { + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + isCustomDnsEnabled = true, + customDnsItems = + listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = true)) + ), + ) + } + + // Assert onNodeWithContentDescription(LOCAL_DNS_SERVER_WARNING).assertExists() } - } @Test - fun testShowSelectedTunnelQuantumOption() { - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault(quantumResistant = QuantumResistantState.On), - ) + fun testShowSelectedTunnelQuantumOption() = + composeExtension.use { + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + quantumResistant = QuantumResistantState.On + ), + ) + } + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG)) + + // Assert + onNodeWithTagAndText(testTag = LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG, text = "On") + .assertExists() } - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG)) - - // Assert - composeTestRule - .onNodeWithTagAndText(testTag = LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG, text = "On") - .assertExists() - } @Test - fun testSelectTunnelQuantumOption() { - // Arrange - val mockSelectQuantumResistantSettingListener: (QuantumResistantState) -> Unit = - mockk(relaxed = true) - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - quantumResistant = QuantumResistantState.Auto, - ), - onSelectQuantumResistanceSetting = mockSelectQuantumResistantSettingListener - ) + fun testSelectTunnelQuantumOption() = + composeExtension.use { + // Arrange + val mockSelectQuantumResistantSettingListener: (QuantumResistantState) -> Unit = + mockk(relaxed = true) + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + quantumResistant = QuantumResistantState.Auto, + ), + onSelectQuantumResistanceSetting = mockSelectQuantumResistantSettingListener, + ) + } + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG)) + + // Assert + onNodeWithTagAndText(testTag = LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG, text = "On") + .performClick() + verify(exactly = 1) { + mockSelectQuantumResistantSettingListener.invoke(QuantumResistantState.On) + } } - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG)) - - // Assert - composeTestRule - .onNodeWithTagAndText(testTag = LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG, text = "On") - .performClick() - verify(exactly = 1) { - mockSelectQuantumResistantSettingListener.invoke(QuantumResistantState.On) - } - } @Test - fun testShowWireguardPortOptions() { - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - selectedWireguardPort = Constraint.Only(Port(53)) - ), - ) + fun testShowWireguardPortOptions() = + composeExtension.use { + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + selectedWireguardPort = Constraint.Only(Port(53)) + ), + ) + } + + // Act + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode( + hasTestTag(String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 53)) + ) + + // Assert + onNodeWithTagAndText( + testTag = String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 51820), + text = "51820" + ) + .assertExists() } - // Act - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode( - hasTestTag(String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 53)) - ) - - // Assert - composeTestRule - .onNodeWithTagAndText( - testTag = String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 51820), - text = "51820" - ) - .assertExists() - } - @Test - fun testSelectWireguardPortOption() { - // Arrange - val mockSelectWireguardPortSelectionListener: (Constraint) -> Unit = - mockk(relaxed = true) - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - selectedWireguardPort = Constraint.Only(Port(53)) - ), - onWireguardPortSelected = mockSelectWireguardPortSelectionListener - ) + fun testSelectWireguardPortOption() = + composeExtension.use { + // Arrange + val mockSelectWireguardPortSelectionListener: (Constraint) -> Unit = + mockk(relaxed = true) + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + selectedWireguardPort = Constraint.Only(Port(53)) + ), + onWireguardPortSelected = mockSelectWireguardPortSelectionListener, + ) + } + + // Act + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode( + hasTestTag(String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 53)) + ) + onNodeWithTagAndText( + testTag = String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 51820), + text = "51820" + ) + .performClick() + + // Assert + verify(exactly = 1) { + mockSelectWireguardPortSelectionListener.invoke(Constraint.Only(Port(51820))) + } } - // Act - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode( - hasTestTag(String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 53)) - ) - composeTestRule - .onNodeWithTagAndText( - testTag = String.format(LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG, 51820), - text = "51820" - ) - .performClick() - - // Assert - verify(exactly = 1) { - mockSelectWireguardPortSelectionListener.invoke(Constraint.Only(Port(51820))) - } - } - @Test - fun testShowWireguardCustomPort() { - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - customWireguardPort = Constraint.Only(Port(4000)) - ), - ) + fun testShowWireguardCustomPort() = + composeExtension.use { + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + customWireguardPort = Constraint.Only(Port(4000)) + ), + ) + } + + // Act + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) + + // Assert + onNodeWithText("4000").assertExists() } - // Act - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) - - // Assert - composeTestRule.onNodeWithText("4000").assertExists() - } - @Test - fun testSelectWireguardCustomPort() { - // Arrange - val onWireguardPortSelected: (Constraint) -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - selectedWireguardPort = Constraint.Only(Port(4000)), - customWireguardPort = Constraint.Only(Port(4000)) - ), - onWireguardPortSelected = onWireguardPortSelected - ) + fun testSelectWireguardCustomPort() = + composeExtension.use { + // Arrange + val onWireguardPortSelected: (Constraint) -> Unit = mockk(relaxed = true) + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + selectedWireguardPort = Constraint.Only(Port(4000)), + customWireguardPort = Constraint.Only(Port(4000)) + ), + onWireguardPortSelected = onWireguardPortSelected + ) + } + + // Act + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) + onNodeWithTag(testTag = LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG).performClick() + + // Assert + verify { onWireguardPortSelected.invoke(Constraint.Only(Port(4000))) } } - // Act - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) - composeTestRule - .onNodeWithTag(testTag = LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG) - .performClick() - - // Assert - verify { onWireguardPortSelected.invoke(Constraint.Only(Port(4000))) } - } - // Navigation Tests @Test - fun testMtuClick() { - // Arrange - val mockedClickHandler: (Int?) -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = VpnSettingsUiState.createDefault(), - navigateToMtuDialog = mockedClickHandler - ) + fun testMtuClick() = + composeExtension.use { + // Arrange + val mockedClickHandler: (Int?) -> Unit = mockk(relaxed = true) + setContentWithTheme { + VpnSettingsScreen( + uiState = VpnSettingsUiState.createDefault(), + navigateToMtuDialog = mockedClickHandler + ) + } + + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG)) + + // Act + onNodeWithText("WireGuard MTU").performClick() + + // Assert + verify { mockedClickHandler.invoke(null) } } - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_LAST_ITEM_TEST_TAG)) - - // Act - composeTestRule.onNodeWithText("WireGuard MTU").performClick() - - // Assert - verify { mockedClickHandler.invoke(null) } - } - @Test - fun testClickAddDns() { - // Arrange - val mockedClickHandler: (Int?, String?) -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = VpnSettingsUiState.createDefault(isCustomDnsEnabled = true), - navigateToDns = mockedClickHandler - ) + fun testClickAddDns() = + composeExtension.use { + // Arrange + val mockedClickHandler: (Int?, String?) -> Unit = mockk(relaxed = true) + setContentWithTheme { + VpnSettingsScreen( + uiState = VpnSettingsUiState.createDefault(isCustomDnsEnabled = true), + navigateToDns = mockedClickHandler + ) + } + + // Act + onNodeWithText("Add a server").performClick() + + // Assert + verify { mockedClickHandler.invoke(null, null) } } - // Act - composeTestRule.onNodeWithText("Add a server").performClick() - - // Assert - verify { mockedClickHandler.invoke(null, null) } - } - @Test - fun testShowTunnelQuantumInfo() { - val mockedShowTunnelQuantumInfoClick: () -> Unit = mockk(relaxed = true) - - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = VpnSettingsUiState.createDefault(), - navigateToQuantumResistanceInfo = mockedShowTunnelQuantumInfoClick - ) + fun testShowTunnelQuantumInfo() = + composeExtension.use { + val mockedShowTunnelQuantumInfoClick: () -> Unit = mockk(relaxed = true) + + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = VpnSettingsUiState.createDefault(), + navigateToQuantumResistanceInfo = mockedShowTunnelQuantumInfoClick + ) + } + + // Act + + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG)) + onNodeWithText("Quantum-resistant tunnel").performClick() + + // Assert + verify(exactly = 1) { mockedShowTunnelQuantumInfoClick() } } - // Act - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG)) - composeTestRule.onNodeWithText("Quantum-resistant tunnel").performClick() - - // Assert - verify(exactly = 1) { mockedShowTunnelQuantumInfoClick() } - } - @Test - fun testShowWireguardPortInfo() { - val mockedClickHandler: (List) -> Unit = mockk(relaxed = true) - - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = VpnSettingsUiState.createDefault(), - navigateToWireguardPortInfo = mockedClickHandler - ) - } + fun testShowWireguardPortInfo() = + composeExtension.use { + val mockedClickHandler: (List) -> Unit = mockk(relaxed = true) - composeTestRule.onNodeWithText("WireGuard port").performClick() + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = VpnSettingsUiState.createDefault(), + navigateToWireguardPortInfo = mockedClickHandler + ) + } - verify(exactly = 1) { mockedClickHandler.invoke(any()) } - } + onNodeWithText("WireGuard port").performClick() - @Test - fun testShowWireguardCustomPortDialog() { - val mockedClickHandler: () -> Unit = mockk(relaxed = true) - - // Arrange - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = VpnSettingsUiState.createDefault(), - navigateToWireguardPortDialog = mockedClickHandler - ) + verify(exactly = 1) { mockedClickHandler.invoke(any()) } } - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG)) - composeTestRule.onNodeWithText("Custom").performClick() - - // Assert - verify(exactly = 1) { mockedClickHandler.invoke() } - } - @Test - fun testClickWireguardCustomPortMainCell() { - // Arrange - val mockOnShowCustomPortDialog: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = VpnSettingsUiState.createDefault(), - navigateToWireguardPortDialog = mockOnShowCustomPortDialog - ) + fun testShowWireguardCustomPortDialog() = + composeExtension.use { + val mockedClickHandler: () -> Unit = mockk(relaxed = true) + + // Arrange + setContentWithTheme { + VpnSettingsScreen( + uiState = VpnSettingsUiState.createDefault(), + navigateToWireguardPortDialog = mockedClickHandler + ) + } + + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG)) + onNodeWithText("Custom").performClick() + + // Assert + verify(exactly = 1) { mockedClickHandler.invoke() } } - // Act - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) - composeTestRule.onNodeWithTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG).performClick() - - // Assert - verify { mockOnShowCustomPortDialog.invoke() } - } - @Test - fun testClickWireguardCustomPortNumberCell() { - // Arrange - val mockOnShowCustomPortDialog: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - VpnSettingsScreen( - uiState = - VpnSettingsUiState.createDefault( - selectedWireguardPort = Constraint.Only(Port(4000)) - ), - navigateToWireguardPortDialog = mockOnShowCustomPortDialog - ) + fun testClickWireguardCustomPortMainCell() = + composeExtension.use { + // Arrange + val mockOnShowCustomPortDialog: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + VpnSettingsScreen( + uiState = VpnSettingsUiState.createDefault(), + navigateToWireguardPortDialog = mockOnShowCustomPortDialog + ) + } + + // Act + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) + onNodeWithTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG).performClick() + + // Assert + verify { mockOnShowCustomPortDialog.invoke() } } - // Act - composeTestRule - .onNodeWithTag(LAZY_LIST_TEST_TAG) - .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) - composeTestRule - .onNodeWithTag(testTag = LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG) - .performClick() - - // Assert - verify { mockOnShowCustomPortDialog.invoke() } - } + @Test + fun testClickWireguardCustomPortNumberCell() = + composeExtension.use { + // Arrange + val mockOnShowCustomPortDialog: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + VpnSettingsScreen( + uiState = + VpnSettingsUiState.createDefault( + selectedWireguardPort = Constraint.Only(Port(4000)) + ), + navigateToWireguardPortDialog = mockOnShowCustomPortDialog + ) + } + + // Act + onNodeWithTag(LAZY_LIST_TEST_TAG) + .performScrollToNode(hasTestTag(LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG)) + onNodeWithTag(testTag = LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG).performClick() + + // Assert + verify { mockOnShowCustomPortDialog.invoke() } + } companion object { private const val LOCAL_DNS_SERVER_WARNING = diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreenTest.kt index e62b1a399b15..b1c9e5f81743 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreenTest.kt @@ -1,9 +1,10 @@ package net.mullvad.mullvadvpn.compose.screen -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import de.mannodermaus.junit5.compose.createComposeExtension import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk @@ -16,59 +17,59 @@ import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct import net.mullvad.mullvadvpn.lib.payment.model.PaymentStatus import net.mullvad.mullvadvpn.lib.payment.model.ProductId import net.mullvad.mullvadvpn.lib.payment.model.ProductPrice -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +@OptIn(ExperimentalTestApi::class) class WelcomeScreenTest { - @get:Rule val composeTestRule = createComposeRule() + @JvmField @RegisterExtension val composeExtension = createComposeExtension() - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) } @Test - fun testDefaultState() { - // Arrange - composeTestRule.setContentWithTheme { - WelcomeScreen( - uiState = WelcomeUiState(), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - navigateToDeviceInfoDialog = {}, - onPurchaseBillingProductClick = { _ -> }, - navigateToVerificationPendingDialog = {} - ) - } + fun testDefaultState() = + composeExtension.use { + // Arrange + setContentWithTheme { + WelcomeScreen( + uiState = WelcomeUiState(), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> }, + navigateToDeviceInfoDialog = {}, + navigateToVerificationPendingDialog = {} + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText("Congrats!").assertExists() onNodeWithText("Here’s your account number. Save it!").assertExists() } - } @Test - fun testDisableSitePayment() { - // Arrange - composeTestRule.setContentWithTheme { - WelcomeScreen( - uiState = WelcomeUiState(), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - navigateToDeviceInfoDialog = {}, - onPurchaseBillingProductClick = { _ -> }, - navigateToVerificationPendingDialog = {} - ) - } + fun testDisableSitePayment() = + composeExtension.use { + // Arrange + setContentWithTheme { + WelcomeScreen( + uiState = WelcomeUiState(), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> }, + navigateToDeviceInfoDialog = {}, + navigateToVerificationPendingDialog = {} + ) + } - // Assert - composeTestRule.apply { + // Assert onNodeWithText( "Either buy credit on our website or redeem a voucher.", substring = true @@ -76,242 +77,251 @@ class WelcomeScreenTest { .assertDoesNotExist() onNodeWithText("Buy credit").assertDoesNotExist() } - } @Test - fun testShowAccountNumber() { - // Arrange - val rawAccountNumber = "1111222233334444" - val expectedAccountNumber = "1111 2222 3333 4444" - composeTestRule.setContentWithTheme { - WelcomeScreen( - uiState = WelcomeUiState(accountNumber = rawAccountNumber), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = { _ -> }, - navigateToDeviceInfoDialog = {}, - navigateToVerificationPendingDialog = {} - ) - } + fun testShowAccountNumber() = + composeExtension.use { + // Arrange + val rawAccountNumber = "1111222233334444" + val expectedAccountNumber = "1111 2222 3333 4444" + setContentWithTheme { + WelcomeScreen( + uiState = WelcomeUiState(accountNumber = rawAccountNumber), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> }, + navigateToDeviceInfoDialog = {}, + navigateToVerificationPendingDialog = {} + ) + } - // Assert - composeTestRule.apply { onNodeWithText(expectedAccountNumber).assertExists() } - } + // Assert + onNodeWithText(expectedAccountNumber).assertExists() + } @Test - fun testClickSitePaymentButton() { - // Arrange - val mockClickListener: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - WelcomeScreen( - uiState = WelcomeUiState(showSitePayment = true), - onSitePaymentClick = mockClickListener, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = { _ -> }, - navigateToVerificationPendingDialog = {}, - navigateToDeviceInfoDialog = {} - ) - } + fun testClickSitePaymentButton() = + composeExtension.use { + // Arrange + val mockClickListener: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + WelcomeScreen( + uiState = WelcomeUiState(showSitePayment = true), + onSitePaymentClick = mockClickListener, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> }, + navigateToDeviceInfoDialog = {}, + navigateToVerificationPendingDialog = {} + ) + } - // Act - composeTestRule.apply { onNodeWithText("Buy credit").performClick() } + // Act + onNodeWithText("Buy credit").performClick() - // Assert - verify(exactly = 1) { mockClickListener.invoke() } - } - - @Test - fun testClickRedeemVoucher() { - // Arrange - val mockClickListener: () -> Unit = mockk(relaxed = true) - composeTestRule.setContentWithTheme { - WelcomeScreen( - uiState = WelcomeUiState(), - onSitePaymentClick = {}, - onRedeemVoucherClick = mockClickListener, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = { _ -> }, - navigateToVerificationPendingDialog = {}, - navigateToDeviceInfoDialog = {} - ) + // Assert + verify(exactly = 1) { mockClickListener.invoke() } } - // Act - composeTestRule.apply { onNodeWithText("Redeem voucher").performClick() } + @Test + fun testClickRedeemVoucher() = + composeExtension.use { + // Arrange + val mockClickListener: () -> Unit = mockk(relaxed = true) + setContentWithTheme { + WelcomeScreen( + uiState = WelcomeUiState(), + onSitePaymentClick = {}, + onRedeemVoucherClick = mockClickListener, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> }, + navigateToDeviceInfoDialog = {}, + navigateToVerificationPendingDialog = {} + ) + } - // Assert - verify(exactly = 1) { mockClickListener.invoke() } - } + // Act + onNodeWithText("Redeem voucher").performClick() - @Test - fun testShowBillingErrorPaymentButton() { - // Arrange - composeTestRule.setContentWithTheme { - WelcomeScreen( - uiState = WelcomeUiState().copy(billingPaymentState = PaymentState.Error.Billing), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = { _ -> }, - navigateToVerificationPendingDialog = {}, - navigateToDeviceInfoDialog = {} - ) + // Assert + verify(exactly = 1) { mockClickListener.invoke() } } - // Assert - composeTestRule.onNodeWithText("Add 30 days time").assertExists() - } - @Test - fun testShowBillingPaymentAvailable() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns null - composeTestRule.setContentWithTheme { - WelcomeScreen( - uiState = - WelcomeUiState( - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) - ), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = { _ -> }, - navigateToVerificationPendingDialog = {}, - navigateToDeviceInfoDialog = {} - ) - } + fun testShowBillingErrorPaymentButton() = + composeExtension.use { + // Arrange + setContentWithTheme { + WelcomeScreen( + uiState = + WelcomeUiState().copy(billingPaymentState = PaymentState.Error.Billing), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> }, + navigateToDeviceInfoDialog = {}, + navigateToVerificationPendingDialog = {} + ) + } - // Assert - composeTestRule.onNodeWithText("Add 30 days time ($10)").assertExists() - } + // Assert + onNodeWithText("Add 30 days time").assertExists() + } @Test - fun testShowPendingPayment() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns PaymentStatus.PENDING - composeTestRule.setContentWithTheme { - WelcomeScreen( - uiState = - WelcomeUiState() - .copy( + fun testShowBillingPaymentAvailable() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns null + setContentWithTheme { + WelcomeScreen( + uiState = + WelcomeUiState( billingPaymentState = PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) ), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = { _ -> }, - navigateToVerificationPendingDialog = {}, - navigateToDeviceInfoDialog = {} - ) + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> }, + navigateToDeviceInfoDialog = {}, + navigateToVerificationPendingDialog = {} + ) + } + + // Assert + onNodeWithText("Add 30 days time ($10)").assertExists() } - // Assert - composeTestRule.onNodeWithText("Google Play payment pending").assertExists() - } + @Test + fun testShowPendingPayment() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns PaymentStatus.PENDING + setContentWithTheme { + WelcomeScreen( + uiState = + WelcomeUiState() + .copy( + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) + ), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> }, + navigateToDeviceInfoDialog = {}, + navigateToVerificationPendingDialog = {} + ) + } + + // Assert + onNodeWithText("Google Play payment pending").assertExists() + } @Test - fun testShowPendingPaymentInfoDialog() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns PaymentStatus.PENDING - val mockShowPendingInfo = mockk<() -> Unit>(relaxed = true) - composeTestRule.setContentWithTheme { - WelcomeScreen( - uiState = - WelcomeUiState() - .copy( - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) - ), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = { _ -> }, - navigateToVerificationPendingDialog = mockShowPendingInfo, - navigateToDeviceInfoDialog = {} - ) + fun testShowPendingPaymentInfoDialog() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns PaymentStatus.PENDING + val mockShowPendingInfo = mockk<() -> Unit>(relaxed = true) + setContentWithTheme { + WelcomeScreen( + uiState = + WelcomeUiState() + .copy( + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) + ), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> }, + navigateToVerificationPendingDialog = mockShowPendingInfo, + navigateToDeviceInfoDialog = {} + ) + } + + // Act + onNodeWithTag(PLAY_PAYMENT_INFO_ICON_TEST_TAG).performClick() + + // Assert + verify(exactly = 1) { mockShowPendingInfo() } } - // Act - composeTestRule.onNodeWithTag(PLAY_PAYMENT_INFO_ICON_TEST_TAG).performClick() + @Test + fun testShowVerificationInProgress() = + composeExtension.use { + // Arrange + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.status } returns PaymentStatus.VERIFICATION_IN_PROGRESS + setContentWithTheme { + WelcomeScreen( + uiState = + WelcomeUiState() + .copy( + billingPaymentState = + PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) + ), + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = { _ -> }, + navigateToDeviceInfoDialog = {}, + navigateToVerificationPendingDialog = {} + ) + } - // Assert - verify(exactly = 1) { mockShowPendingInfo() } - } + // Assert + onNodeWithText("Verifying purchase").assertExists() + } @Test - fun testShowVerificationInProgress() { - // Arrange - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.status } returns PaymentStatus.VERIFICATION_IN_PROGRESS - composeTestRule.setContentWithTheme { - WelcomeScreen( - uiState = - WelcomeUiState() - .copy( + fun testOnPurchaseBillingProductClick() = + composeExtension.use { + // Arrange + val clickHandler: (ProductId) -> Unit = mockk(relaxed = true) + val mockPaymentProduct: PaymentProduct = mockk() + every { mockPaymentProduct.price } returns ProductPrice("$10") + every { mockPaymentProduct.productId } returns ProductId("PRODUCT_ID") + every { mockPaymentProduct.status } returns null + setContentWithTheme { + WelcomeScreen( + uiState = + WelcomeUiState( billingPaymentState = PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) ), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = { _ -> }, - navigateToVerificationPendingDialog = {}, - navigateToDeviceInfoDialog = {} - ) - } + onSitePaymentClick = {}, + onRedeemVoucherClick = {}, + onSettingsClick = {}, + onAccountClick = {}, + onPurchaseBillingProductClick = clickHandler, + navigateToDeviceInfoDialog = {}, + navigateToVerificationPendingDialog = {} + ) + } - // Assert - composeTestRule.onNodeWithText("Verifying purchase").assertExists() - } + // Act + onNodeWithText("Add 30 days time ($10)").performClick() - @Test - fun testOnPurchaseBillingProductClick() { - // Arrange - val clickHandler: (ProductId) -> Unit = mockk(relaxed = true) - val mockPaymentProduct: PaymentProduct = mockk() - every { mockPaymentProduct.price } returns ProductPrice("$10") - every { mockPaymentProduct.productId } returns ProductId("PRODUCT_ID") - every { mockPaymentProduct.status } returns null - composeTestRule.setContentWithTheme { - WelcomeScreen( - uiState = - WelcomeUiState( - billingPaymentState = - PaymentState.PaymentAvailable(listOf(mockPaymentProduct)) - ), - onSitePaymentClick = {}, - onRedeemVoucherClick = {}, - onSettingsClick = {}, - onAccountClick = {}, - onPurchaseBillingProductClick = clickHandler, - navigateToVerificationPendingDialog = {}, - navigateToDeviceInfoDialog = {} - ) + // Assert + verify { clickHandler(ProductId("PRODUCT_ID")) } } - - // Act - composeTestRule.onNodeWithText("Add 30 days time ($10)").performClick() - - // Assert - verify { clickHandler(ProductId("PRODUCT_ID")) } - } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt index b9db62d620dd..864f909421ce 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt @@ -96,7 +96,7 @@ fun FilterScreen( onApplyClick: () -> Unit = {}, onSelectedOwnership: (ownership: Ownership?) -> Unit = {}, onAllProviderCheckChange: (isChecked: Boolean) -> Unit = {}, - onSelectedProvider: (checked: Boolean, provider: Provider) -> Unit + onSelectedProvider: (checked: Boolean, provider: Provider) -> Unit = { _, _ -> } ) { var providerExpanded by rememberSaveable { mutableStateOf(false) } var ownershipExpanded by rememberSaveable { mutableStateOf(false) } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt index 30b54cea114f..9767d3930a2f 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt @@ -9,6 +9,7 @@ import kotlin.test.assertEquals import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.repository.InAppNotification @@ -19,13 +20,13 @@ import net.mullvad.mullvadvpn.usecase.TunnelStateNotificationUseCase import net.mullvad.mullvadvpn.usecase.VersionNotificationUseCase import net.mullvad.talpid.tunnel.ErrorState import org.joda.time.DateTime -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class InAppNotificationControllerTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private lateinit var inAppNotificationController: InAppNotificationController private val accountExpiryNotifications = MutableStateFlow(emptyList()) @@ -35,7 +36,7 @@ class InAppNotificationControllerTest { private lateinit var job: Job - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) @@ -56,11 +57,11 @@ class InAppNotificationControllerTest { newDeviceNotificationUseCase, versionNotificationUseCase, tunnelStateNotificationUseCase, - CoroutineScope(job + testCoroutineRule.testDispatcher) + CoroutineScope(job + UnconfinedTestDispatcher()) ) } - @After + @AfterEach fun teardown() { job.cancel() unmockkAll() diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt index 3e26a37c2c12..3296243c562d 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt @@ -12,23 +12,23 @@ import io.mockk.unmockkAll import io.mockk.verify import kotlin.test.assertEquals import kotlin.test.assertFails -import org.junit.After -import org.junit.Before -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test class ApplicationsIconManagerTest { private val mockedPackageManager = mockk() private val mockedMainLooper = mockk() private val testSubject = ApplicationsIconManager(mockedPackageManager) - @Before + @BeforeEach fun setUp() { mockkStatic(Looper::class) mockkStatic(DRAWABLE_EXTENSION_CLASS) every { Looper.getMainLooper() } returns mockedMainLooper } - @After + @AfterEach fun tearDown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt index a74ab85fc4d8..1b37184915ee 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt @@ -8,8 +8,8 @@ import io.mockk.mockk import io.mockk.unmockkAll import io.mockk.verifyAll import net.mullvad.mullvadvpn.lib.common.test.assertLists -import org.junit.After -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test class ApplicationsProviderTest { private val mockedPackageManager = mockk() @@ -17,7 +17,7 @@ class ApplicationsProviderTest { private val testSubject = ApplicationsProvider(mockedPackageManager, selfPackageName) private val internet = Manifest.permission.INTERNET - @After + @AfterEach fun tearDown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt index 2ef1d1f02c20..dc9c141f28ab 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt @@ -2,13 +2,13 @@ package net.mullvad.mullvadvpn.relaylist import io.mockk.mockk import io.mockk.unmockkAll -import org.junit.After -import org.junit.Assert.assertTrue -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test class RelayNameComparatorTest { - @After + @AfterEach fun tearDown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxyTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxyTest.kt index 52bbbee0a691..4da6c4fdafa1 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxyTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxyTest.kt @@ -19,9 +19,9 @@ import kotlin.test.assertEquals import net.mullvad.mullvadvpn.lib.ipc.Event import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher import net.mullvad.mullvadvpn.lib.ipc.Request -import org.junit.After -import org.junit.Before -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test class ConnectionProxyTest { @@ -32,7 +32,7 @@ class ConnectionProxyTest { @MockK private lateinit var mockedDispatchingHandler: EventDispatcher lateinit var connectionProxy: ConnectionProxy - @Before + @BeforeEach fun setup() { mockkStatic(Looper::class) mockkStatic(Log::class) @@ -44,7 +44,7 @@ class ConnectionProxyTest { every { Log.e(any(), any()) } returns mockk(relaxed = true) } - @After + @AfterEach fun tearDown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSourceTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSourceTest.kt index 50f930d2a530..d4fd34943889 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSourceTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSourceTest.kt @@ -17,9 +17,9 @@ import net.mullvad.mullvadvpn.lib.common.util.JobTracker import net.mullvad.mullvadvpn.lib.ipc.Event import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher import net.mullvad.mullvadvpn.lib.ipc.Request -import org.junit.After -import org.junit.Before -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test class ServiceConnectionDeviceDataSourceTest { private val tracker = JobTracker() @@ -32,7 +32,7 @@ class ServiceConnectionDeviceDataSourceTest { lateinit var serviceConnectionDeviceDataSource: ServiceConnectionDeviceDataSource - @Before + @BeforeEach fun setup() { mockkStatic(Looper::class) mockkStatic(android.util.Log::class) @@ -44,7 +44,7 @@ class ServiceConnectionDeviceDataSourceTest { every { android.util.Log.e(any(), any()) } returns mockk(relaxed = true) } - @After + @AfterEach fun tearDown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt index 5341708d3bde..f068642bec10 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt @@ -14,18 +14,18 @@ import net.mullvad.mullvadvpn.model.AccountExpiry import net.mullvad.mullvadvpn.repository.AccountRepository import net.mullvad.mullvadvpn.repository.InAppNotification import org.joda.time.DateTime -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class AccountExpiryNotificationUseCaseTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val accountExpiry = MutableStateFlow(AccountExpiry.Missing) private lateinit var accountExpiryNotificationUseCase: AccountExpiryNotificationUseCase - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) @@ -35,7 +35,7 @@ class AccountExpiryNotificationUseCaseTest { accountExpiryNotificationUseCase = AccountExpiryNotificationUseCase(accountRepository) } - @After + @AfterEach fun teardown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt index bd375d729a5c..5e4403c38db4 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt @@ -15,13 +15,13 @@ import net.mullvad.mullvadvpn.model.Device import net.mullvad.mullvadvpn.model.DeviceState import net.mullvad.mullvadvpn.repository.DeviceRepository import net.mullvad.mullvadvpn.repository.InAppNotification -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class NewDeviceUseNotificationCaseTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val deviceName = "Frank Zebra" private val deviceState = @@ -32,7 +32,7 @@ class NewDeviceUseNotificationCaseTest { ) private lateinit var newDeviceNotificationUseCase: NewDeviceNotificationUseCase - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) @@ -42,7 +42,7 @@ class NewDeviceUseNotificationCaseTest { NewDeviceNotificationUseCase(deviceRepository = mockDeviceRepository) } - @After + @AfterEach fun teardown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt index bdc5ea49b859..6563f7e6e9ff 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt @@ -16,8 +16,8 @@ import net.mullvad.mullvadvpn.repository.AccountRepository import net.mullvad.talpid.tunnel.ErrorState import net.mullvad.talpid.tunnel.ErrorStateCause import org.joda.time.DateTime -import org.junit.Before -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test class OutOfTimeUseCaseTest { private val mockAccountRepository: AccountRepository = mockk() @@ -28,7 +28,7 @@ class OutOfTimeUseCaseTest { lateinit var outOfTimeUseCase: OutOfTimeUseCase - @Before + @BeforeEach fun setup() { every { mockAccountRepository.accountExpiryState } returns expiry every { mockMessageHandler.events() } returns events diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/PlayPaymentUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/PlayPaymentUseCaseTest.kt index a1d8bee37a41..fb85629a9cb0 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/PlayPaymentUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/PlayPaymentUseCaseTest.kt @@ -12,7 +12,7 @@ import net.mullvad.mullvadvpn.lib.payment.PaymentRepository import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability import net.mullvad.mullvadvpn.lib.payment.model.ProductId import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult -import org.junit.Test +import org.junit.jupiter.api.Test class PlayPaymentUseCaseTest { diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt index e67630642dc9..244c735ee986 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt @@ -19,13 +19,13 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState import net.mullvad.talpid.tunnel.ActionAfterDisconnect import net.mullvad.talpid.tunnel.ErrorState import net.mullvad.talpid.util.EventNotifier -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class TunnelStateNotificationUseCaseTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockServiceConnectionManager: ServiceConnectionManager = mockk() private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk() @@ -37,7 +37,7 @@ class TunnelStateNotificationUseCaseTest { private val eventNotifierTunnelUiState = EventNotifier(TunnelState.Disconnected()) - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) every { mockConnectionProxy.onUiStateChange } returns eventNotifierTunnelUiState @@ -49,7 +49,7 @@ class TunnelStateNotificationUseCaseTest { TunnelStateNotificationUseCase(serviceConnectionManager = mockServiceConnectionManager) } - @After + @AfterEach fun teardown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt index 5aba70c9384b..12f720728058 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt @@ -18,13 +18,13 @@ 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.util.appVersionCallbackFlow -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class VersionNotificationUseCaseTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockServiceConnectionManager: ServiceConnectionManager = mockk() private lateinit var mockAppVersionInfoCache: AppVersionInfoCache @@ -43,7 +43,7 @@ class VersionNotificationUseCaseTest { ) private lateinit var versionNotificationUseCase: VersionNotificationUseCase - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) mockkStatic(CACHE_EXTENSION_CLASS) @@ -63,7 +63,7 @@ class VersionNotificationUseCaseTest { ) } - @After + @AfterEach fun teardown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/utils/VoucherRegexHelperParameterizedTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/utils/VoucherRegexHelperParameterizedTest.kt index 60db08b85f5a..1722e81a9d98 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/utils/VoucherRegexHelperParameterizedTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/utils/VoucherRegexHelperParameterizedTest.kt @@ -1,34 +1,29 @@ package net.mullvad.mullvadvpn.utils +import java.util.stream.Stream +import kotlin.test.assertEquals 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 +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource private const val IS_ACCEPTED_FORMAT = true private const val IS_UNACCEPTED_FORMAT = false -@RunWith(Parameterized::class) -class VoucherRegexHelperParameterizedTest( - private val isValid: Boolean, - private val voucher: String -) { - @get:Rule val testCoroutineRule = TestCoroutineRule() +@ExtendWith(TestCoroutineRule::class) +class VoucherRegexHelperParameterizedTest { - @Test - fun testVoucherFormat() { - assertThat(VoucherRegexHelper.validate(voucher), equalTo(isValid)) + @ParameterizedTest + @MethodSource("data") + fun testVoucherFormat(isValid: Boolean, voucher: String) { + assertEquals(VoucherRegexHelper.validate(voucher), isValid) } companion object { @JvmStatic - @Parameterized.Parameters - fun data(): Collection> = - listOf( + fun data(): Stream> = + Stream.of( arrayOf(IS_ACCEPTED_FORMAT, "1"), arrayOf(IS_ACCEPTED_FORMAT, "a"), arrayOf(IS_ACCEPTED_FORMAT, "A"), diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt index 22acc979868a..637c5310396f 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt @@ -30,13 +30,13 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.AuthTokenCache import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache import net.mullvad.mullvadvpn.usecase.PaymentUseCase -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class AccountViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockAccountRepository: AccountRepository = mockk(relaxUnitFun = true) private val mockServiceConnectionManager: ServiceConnectionManager = mockk() @@ -62,7 +62,7 @@ class AccountViewModelTest { private lateinit var viewModel: AccountViewModel - @Before + @BeforeEach fun setUp() { mockkStatic(CACHE_EXTENSION_CLASS) mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS) @@ -82,7 +82,7 @@ class AccountViewModelTest { ) } - @After + @AfterEach fun tearDown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ChangelogViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ChangelogViewModelTest.kt index 3350178ca323..d9a5ae534e60 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ChangelogViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ChangelogViewModelTest.kt @@ -12,19 +12,19 @@ import kotlin.test.assertNotNull import kotlinx.coroutines.test.runTest import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.repository.ChangelogRepository -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class ChangelogViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() @MockK private lateinit var mockedChangelogRepository: ChangelogRepository private lateinit var viewModel: ChangelogViewModel - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) mockkStatic(EVENT_NOTIFIER_EXTENSION_CLASS) @@ -32,7 +32,7 @@ class ChangelogViewModelTest { Runs } - @After + @AfterEach fun teardown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt index 47364652ceda..a1e3ce57e57b 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt @@ -43,13 +43,13 @@ import net.mullvad.mullvadvpn.usecase.RelayListUseCase import net.mullvad.mullvadvpn.util.appVersionCallbackFlow import net.mullvad.talpid.tunnel.ErrorState import net.mullvad.talpid.util.EventNotifier -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class ConnectViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockServiceConnectionManager: ServiceConnectionManager = mockk() private lateinit var viewModel: ConnectViewModel @@ -102,7 +102,7 @@ class ConnectViewModelTest { private val outOfTimeUseCase: OutOfTimeUseCase = mockk() private val outOfTimeViewFlow = MutableStateFlow(false) - @Before + @BeforeEach fun setup() { mockkStatic(CACHE_EXTENSION_CLASS) mockkStatic(SERVICE_CONNECTION_MANAGER_EXTENSIONS) @@ -148,7 +148,7 @@ class ConnectViewModelTest { ) } - @After + @AfterEach fun teardown() { viewModel.viewModelScope.coroutineContext.cancel() unmockkAll() @@ -160,83 +160,79 @@ class ConnectViewModelTest { } @Test - fun testTunnelRealStateUpdate() = - runTest(testCoroutineRule.testDispatcher) { - val tunnelRealStateTestItem = TunnelState.Connected(mockk(relaxed = true), null) - - viewModel.uiState.test { - assertEquals(ConnectUiState.INITIAL, awaitItem()) - serviceConnectionState.value = - ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) - eventNotifierTunnelRealState.notify(tunnelRealStateTestItem) - val result = awaitItem() - assertEquals(tunnelRealStateTestItem, result.tunnelRealState) - } + fun testTunnelRealStateUpdate() = runTest { + val tunnelRealStateTestItem = TunnelState.Connected(mockk(relaxed = true), null) + + viewModel.uiState.test { + assertEquals(ConnectUiState.INITIAL, awaitItem()) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + eventNotifierTunnelRealState.notify(tunnelRealStateTestItem) + val result = awaitItem() + assertEquals(tunnelRealStateTestItem, result.tunnelRealState) } + } @Test - fun testTunnelUiStateUpdate() = - runTest(testCoroutineRule.testDispatcher) { - val tunnelUiStateTestItem = TunnelState.Connected(mockk(), mockk()) - - viewModel.uiState.test { - assertEquals(ConnectUiState.INITIAL, awaitItem()) - serviceConnectionState.value = - ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) - eventNotifierTunnelUiState.notify(tunnelUiStateTestItem) - val result = awaitItem() - assertEquals(tunnelUiStateTestItem, result.tunnelUiState) - } + fun testTunnelUiStateUpdate() = runTest { + val tunnelUiStateTestItem = TunnelState.Connected(mockk(), mockk()) + + viewModel.uiState.test { + assertEquals(ConnectUiState.INITIAL, awaitItem()) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + eventNotifierTunnelUiState.notify(tunnelUiStateTestItem) + val result = awaitItem() + assertEquals(tunnelUiStateTestItem, result.tunnelUiState) } + } @Test - fun testRelayItemUpdate() = - runTest(testCoroutineRule.testDispatcher) { - val relayTestItem = - RelayCountry(name = "Name", code = "Code", expanded = false, cities = emptyList()) - selectedRelayFlow.value = relayTestItem - - viewModel.uiState.test { - assertEquals(ConnectUiState.INITIAL, awaitItem()) - serviceConnectionState.value = - ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) - val result = awaitItem() - assertEquals(relayTestItem, result.relayLocation) - } + fun testRelayItemUpdate() = runTest { + val relayTestItem = + RelayCountry(name = "Name", code = "Code", expanded = false, cities = emptyList()) + selectedRelayFlow.value = relayTestItem + + viewModel.uiState.test { + assertEquals(ConnectUiState.INITIAL, awaitItem()) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + val result = awaitItem() + assertEquals(relayTestItem, result.relayLocation) } + } @Test - fun testLocationUpdate() = - runTest(testCoroutineRule.testDispatcher) { - val locationTestItem = - GeoIpLocation( - ipv4 = mockk(relaxed = true), - ipv6 = mockk(relaxed = true), - country = "Sweden", - city = "Gothenburg", - hostname = "Host" - ) + fun testLocationUpdate() = runTest { + val locationTestItem = + GeoIpLocation( + ipv4 = mockk(relaxed = true), + ipv6 = mockk(relaxed = true), + country = "Sweden", + city = "Gothenburg", + hostname = "Host" + ) - // Act, Assert - viewModel.uiState.test { - assertEquals(ConnectUiState.INITIAL, awaitItem()) - eventNotifierTunnelRealState.notify(TunnelState.Disconnected(null)) + // Act, Assert + viewModel.uiState.test { + assertEquals(ConnectUiState.INITIAL, awaitItem()) + eventNotifierTunnelRealState.notify(TunnelState.Disconnected(null)) - serviceConnectionState.value = - ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) - // Start of with no location - assertNull(awaitItem().location) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + // Start of with no location + assertNull(awaitItem().location) - // After updated we show latest - eventNotifierTunnelRealState.notify(TunnelState.Disconnected(locationTestItem)) - assertEquals(locationTestItem, awaitItem().location) - } + // After updated we show latest + eventNotifierTunnelRealState.notify(TunnelState.Disconnected(locationTestItem)) + assertEquals(locationTestItem, awaitItem().location) } + } @Test fun testLocationUpdateNullLocation() = // Arrange - runTest(testCoroutineRule.testDispatcher) { + runTest { val locationTestItem = null // Act, Assert @@ -251,99 +247,91 @@ class ConnectViewModelTest { } @Test - fun testOnDisconnectClick() = - runTest(testCoroutineRule.testDispatcher) { - val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true) - every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy - viewModel.onDisconnectClick() - verify { mockConnectionProxy.disconnect() } - } + fun testOnDisconnectClick() = runTest { + val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true) + every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy + viewModel.onDisconnectClick() + verify { mockConnectionProxy.disconnect() } + } @Test - fun testOnReconnectClick() = - runTest(testCoroutineRule.testDispatcher) { - val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true) - every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy - viewModel.onReconnectClick() - verify { mockConnectionProxy.reconnect() } - } + fun testOnReconnectClick() = runTest { + val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true) + every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy + viewModel.onReconnectClick() + verify { mockConnectionProxy.reconnect() } + } @Test - fun testOnConnectClick() = - runTest(testCoroutineRule.testDispatcher) { - val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true) - every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy - viewModel.onConnectClick() - verify { mockConnectionProxy.connect() } - } + fun testOnConnectClick() = runTest { + val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true) + every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy + viewModel.onConnectClick() + verify { mockConnectionProxy.connect() } + } @Test - fun testOnCancelClick() = - runTest(testCoroutineRule.testDispatcher) { - val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true) - every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy - viewModel.onCancelClick() - verify { mockConnectionProxy.disconnect() } - } + fun testOnCancelClick() = runTest { + val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true) + every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy + viewModel.onCancelClick() + verify { mockConnectionProxy.disconnect() } + } @Test - fun testErrorNotificationState() = - runTest(testCoroutineRule.testDispatcher) { - // Arrange - val mockErrorState: ErrorState = mockk() - val expectedConnectNotificationState = - InAppNotification.TunnelStateError(mockErrorState) - val tunnelUiState = TunnelState.Error(mockErrorState) - notifications.value = listOf(expectedConnectNotificationState) - - // Act, Assert - viewModel.uiState.test { - assertEquals(ConnectUiState.INITIAL, awaitItem()) - serviceConnectionState.value = - ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) - eventNotifierTunnelUiState.notify(tunnelUiState) - val result = awaitItem() - assertEquals(expectedConnectNotificationState, result.inAppNotification) - } + fun testErrorNotificationState() = runTest { + // Arrange + val mockErrorState: ErrorState = mockk() + val expectedConnectNotificationState = InAppNotification.TunnelStateError(mockErrorState) + val tunnelUiState = TunnelState.Error(mockErrorState) + notifications.value = listOf(expectedConnectNotificationState) + + // Act, Assert + viewModel.uiState.test { + assertEquals(ConnectUiState.INITIAL, awaitItem()) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + eventNotifierTunnelUiState.notify(tunnelUiState) + val result = awaitItem() + assertEquals(expectedConnectNotificationState, result.inAppNotification) } + } @Test - fun testOnShowAccountClick() = - runTest(testCoroutineRule.testDispatcher) { - // Arrange - val mockToken = "4444 5555 6666 7777" - val mockAuthTokenCache: AuthTokenCache = mockk(relaxed = true) - every { mockServiceConnectionManager.authTokenCache() } returns mockAuthTokenCache - coEvery { mockAuthTokenCache.fetchAuthToken() } returns mockToken - - // Act, Assert - viewModel.uiSideEffect.test { - viewModel.onManageAccountClick() - val action = awaitItem() - assertIs(action) - assertEquals(mockToken, action.token) - } + fun testOnShowAccountClick() = runTest { + // Arrange + val mockToken = "4444 5555 6666 7777" + val mockAuthTokenCache: AuthTokenCache = mockk(relaxed = true) + every { mockServiceConnectionManager.authTokenCache() } returns mockAuthTokenCache + coEvery { mockAuthTokenCache.fetchAuthToken() } returns mockToken + + // Act, Assert + viewModel.uiSideEffect.test { + viewModel.onManageAccountClick() + val action = awaitItem() + assertIs(action) + assertEquals(mockToken, action.token) } + } @Test - fun testOutOfTimeUiSideEffect() = - runTest(testCoroutineRule.testDispatcher) { - // Arrange - val deferred = async { viewModel.uiSideEffect.first() } - - // Act - viewModel.uiState.test { - awaitItem() - serviceConnectionState.value = - ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) - outOfTimeViewFlow.value = true - awaitItem() - } - - // Assert - assertIs(deferred.await()) + fun testOutOfTimeUiSideEffect() = runTest { + // Arrange + val deferred = async { viewModel.uiSideEffect.first() } + + // Act + viewModel.uiState.test { + awaitItem() + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + outOfTimeViewFlow.value = true + awaitItem() } + // Assert + assertIs(deferred.await()) + } + companion object { private const val CACHE_EXTENSION_CLASS = "net.mullvad.mullvadvpn.util.CacheExtensionsKt" private const val SERVICE_CONNECTION_MANAGER_EXTENSIONS = diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt index 0fcf684afcfb..3f7966e8bd71 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt @@ -11,7 +11,6 @@ import io.mockk.mockkStatic import io.mockk.unmockkAll import io.mockk.verify import io.mockk.verifyOrder -import junit.framework.Assert.assertEquals import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -25,13 +24,14 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState import net.mullvad.talpid.util.EventNotifier import net.mullvad.talpid.util.callbackFlowFromSubscription -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class DeviceRevokedViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() @MockK private lateinit var mockedAccountRepository: AccountRepository @@ -42,7 +42,7 @@ class DeviceRevokedViewModelTest { private lateinit var viewModel: DeviceRevokedViewModel - @Before + @BeforeEach fun setup() { MockKAnnotations.init(this) mockkStatic(EVENT_NOTIFIER_EXTENSION_CLASS) @@ -55,7 +55,7 @@ class DeviceRevokedViewModelTest { ) } - @After + @AfterEach fun teardown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt index 9f61ea4a91b2..210ed8866628 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt @@ -20,13 +20,13 @@ import net.mullvad.mullvadvpn.model.Ownership import net.mullvad.mullvadvpn.model.Providers import net.mullvad.mullvadvpn.relaylist.Provider import net.mullvad.mullvadvpn.usecase.RelayListFilterUseCase -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class FilterViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockRelayListFilterUseCase: RelayListFilterUseCase = mockk(relaxed = true) private lateinit var viewModel: FilterViewModel private val selectedOwnership = @@ -54,7 +54,7 @@ class FilterViewModelTest { private val mockSelectedProviders: List = listOf(Provider("31173", true), Provider("Blix", true), Provider("Creanova", true)) - @Before + @BeforeEach fun setup() { every { mockRelayListFilterUseCase.selectedOwnership() } returns selectedOwnership every { mockRelayListFilterUseCase.availableProviders() } returns @@ -64,7 +64,7 @@ class FilterViewModelTest { viewModel = FilterViewModel(mockRelayListFilterUseCase) } - @After + @AfterEach fun teardown() { viewModel.viewModelScope.coroutineContext.cancel() unmockkAll() 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 c402a3103e6f..f677615c2494 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 @@ -30,13 +30,13 @@ import net.mullvad.mullvadvpn.repository.DeviceRepository import net.mullvad.mullvadvpn.usecase.ConnectivityUseCase import net.mullvad.mullvadvpn.usecase.NewDeviceNotificationUseCase import org.joda.time.DateTime -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class LoginViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() @MockK private lateinit var connectivityUseCase: ConnectivityUseCase @MockK private lateinit var mockedAccountRepository: AccountRepository @@ -46,7 +46,7 @@ class LoginViewModelTest { private lateinit var loginViewModel: LoginViewModel private val accountHistoryTestEvents = MutableStateFlow(AccountHistory.Missing) - @Before + @BeforeEach fun setup() { Dispatchers.setMain(UnconfinedTestDispatcher()) diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt index 50a9d8fb981a..0df34e074725 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt @@ -38,13 +38,13 @@ import net.mullvad.mullvadvpn.usecase.PaymentUseCase import net.mullvad.talpid.util.EventNotifier import org.joda.time.DateTime import org.joda.time.ReadableInstant -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class OutOfTimeViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val serviceConnectionStateFlow = MutableStateFlow(ServiceConnectionState.Disconnected) @@ -70,7 +70,7 @@ class OutOfTimeViewModelTest { private lateinit var viewModel: OutOfTimeViewModel - @Before + @BeforeEach fun setUp() { mockkStatic(SERVICE_CONNECTION_MANAGER_EXTENSIONS) mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS) @@ -103,75 +103,71 @@ class OutOfTimeViewModelTest { ) } - @After + @AfterEach fun tearDown() { viewModel.viewModelScope.coroutineContext.cancel() unmockkAll() } @Test - fun testSitePaymentClick() = - runTest(testCoroutineRule.testDispatcher) { - // Arrange - val mockToken = "4444 5555 6666 7777" - val mockAuthTokenCache: AuthTokenCache = mockk(relaxed = true) - every { mockServiceConnectionManager.authTokenCache() } returns mockAuthTokenCache - coEvery { mockAuthTokenCache.fetchAuthToken() } returns mockToken - - // Act, Assert - viewModel.uiSideEffect.test { - viewModel.onSitePaymentClick() - val action = awaitItem() - assertIs(action) - assertEquals(mockToken, action.token) - } + fun testSitePaymentClick() = runTest { + // Arrange + val mockToken = "4444 5555 6666 7777" + val mockAuthTokenCache: AuthTokenCache = mockk(relaxed = true) + every { mockServiceConnectionManager.authTokenCache() } returns mockAuthTokenCache + coEvery { mockAuthTokenCache.fetchAuthToken() } returns mockToken + + // Act, Assert + viewModel.uiSideEffect.test { + viewModel.onSitePaymentClick() + val action = awaitItem() + assertIs(action) + assertEquals(mockToken, action.token) } + } @Test - fun testUpdateTunnelState() = - runTest(testCoroutineRule.testDispatcher) { - // Arrange - val tunnelRealStateTestItem = TunnelState.Connected(mockk(), mockk()) - - // Act, Assert - viewModel.uiState.test { - assertEquals(OutOfTimeUiState(deviceName = ""), awaitItem()) - eventNotifierTunnelRealState.notify(tunnelRealStateTestItem) - serviceConnectionStateFlow.value = - ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) - val result = awaitItem() - assertEquals(tunnelRealStateTestItem, result.tunnelState) - } + fun testUpdateTunnelState() = runTest { + // Arrange + val tunnelRealStateTestItem = TunnelState.Connected(mockk(), mockk()) + + // Act, Assert + viewModel.uiState.test { + assertEquals(OutOfTimeUiState(deviceName = ""), awaitItem()) + eventNotifierTunnelRealState.notify(tunnelRealStateTestItem) + serviceConnectionStateFlow.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + val result = awaitItem() + assertEquals(tunnelRealStateTestItem, result.tunnelState) } + } @Test - fun testOpenConnectScreen() = - runTest(testCoroutineRule.testDispatcher) { - // Arrange - val mockExpiryDate: DateTime = mockk() - every { mockExpiryDate.isAfter(any()) } returns true - - // Act, Assert - viewModel.uiSideEffect.test { - outOfTimeFlow.value = false - val action = awaitItem() - assertIs(action) - } + fun testOpenConnectScreen() = runTest { + // Arrange + val mockExpiryDate: DateTime = mockk() + every { mockExpiryDate.isAfter(any()) } returns true + + // Act, Assert + viewModel.uiSideEffect.test { + outOfTimeFlow.value = false + val action = awaitItem() + assertIs(action) } + } @Test - fun testOnDisconnectClick() = - runTest(testCoroutineRule.testDispatcher) { - // Arrange - val mockProxy: ConnectionProxy = mockk(relaxed = true) - every { mockServiceConnectionManager.connectionProxy() } returns mockProxy + fun testOnDisconnectClick() = runTest { + // Arrange + val mockProxy: ConnectionProxy = mockk(relaxed = true) + every { mockServiceConnectionManager.connectionProxy() } returns mockProxy - // Act - viewModel.onDisconnectClick() + // Act + viewModel.onDisconnectClick() - // Assert - verify { mockProxy.disconnect() } - } + // Assert + verify { mockProxy.disconnect() } + } @Test fun testBillingProductsUnavailableState() = runTest { diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/PaymentViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/PaymentViewModelTest.kt index 665e23c3d42d..1e2fba3c960b 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/PaymentViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/PaymentViewModelTest.kt @@ -12,13 +12,13 @@ import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult import net.mullvad.mullvadvpn.usecase.PaymentUseCase import net.mullvad.mullvadvpn.util.toPaymentDialogData -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class PaymentViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true) @@ -26,14 +26,14 @@ class PaymentViewModelTest { private lateinit var viewModel: PaymentViewModel - @Before + @BeforeEach fun setUp() { coEvery { mockPaymentUseCase.purchaseResult } returns purchaseResult viewModel = PaymentViewModel(paymentUseCase = mockPaymentUseCase) } - @After + @AfterEach fun tearDown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModelTest.kt index 5726c6249c96..6ea1a85ed20d 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModelTest.kt @@ -15,13 +15,13 @@ import net.mullvad.mullvadvpn.dataproxy.SendProblemReportResult import net.mullvad.mullvadvpn.dataproxy.UserReport import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.repository.ProblemReportRepository -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class ReportProblemViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() @MockK private lateinit var mockMullvadProblemReport: MullvadProblemReport @@ -31,7 +31,7 @@ class ReportProblemViewModelTest { private lateinit var viewModel: ReportProblemViewModel - @Before + @BeforeEach fun setUp() { MockKAnnotations.init(this) coEvery { mockMullvadProblemReport.collectLogs() } returns true @@ -39,7 +39,7 @@ class ReportProblemViewModelTest { viewModel = ReportProblemViewModel(mockMullvadProblemReport, mockProblemReportRepository) } - @After + @AfterEach fun tearDown() { viewModel.viewModelScope.coroutineContext.cancel() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt index 5ad1af11823f..46ea0bf3fb68 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt @@ -29,13 +29,13 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.ui.serviceconnection.connectionProxy import net.mullvad.mullvadvpn.usecase.RelayListFilterUseCase import net.mullvad.mullvadvpn.usecase.RelayListUseCase -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class SelectLocationViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockRelayListFilterUseCase: RelayListFilterUseCase = mockk(relaxed = true) private val mockServiceConnectionManager: ServiceConnectionManager = mockk() @@ -46,7 +46,7 @@ class SelectLocationViewModelTest { private val selectedProvider = MutableStateFlow>(Constraint.Any()) private val allProvider = MutableStateFlow>(emptyList()) - @Before + @BeforeEach fun setup() { every { mockRelayListFilterUseCase.selectedOwnership() } returns selectedOwnership @@ -64,7 +64,7 @@ class SelectLocationViewModelTest { ) } - @After + @AfterEach fun teardown() { viewModel.viewModelScope.coroutineContext.cancel() unmockkAll() diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt index ff6804c95345..b52ab53ef6e0 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt @@ -19,13 +19,13 @@ 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.util.appVersionCallbackFlow -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class SettingsViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockDeviceRepository: DeviceRepository = mockk() private val mockServiceConnectionManager: ServiceConnectionManager = mockk() @@ -46,7 +46,7 @@ class SettingsViewModelTest { private lateinit var viewModel: SettingsViewModel - @Before + @BeforeEach fun setUp() { mockkStatic(CACHE_EXTENSION_CLASS) val deviceState = MutableStateFlow(DeviceState.LoggedOut) @@ -68,7 +68,7 @@ class SettingsViewModelTest { ) } - @After + @AfterEach fun tearDown() { viewModel.viewModelScope.coroutineContext.cancel() unmockkAll() diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt index 5409b7f736ac..7b2b4cacd5fb 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt @@ -15,6 +15,7 @@ import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import net.mullvad.mullvadvpn.applist.AppData import net.mullvad.mullvadvpn.applist.ApplicationsProvider @@ -24,174 +25,169 @@ 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.SplitTunneling -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.rules.Timeout - +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(TestCoroutineRule::class) +@Timeout(3000L, unit = TimeUnit.MILLISECONDS) class SplitTunnelingViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() - @get:Rule val timeout = Timeout(3000L, TimeUnit.MILLISECONDS) private val mockedApplicationsProvider = mockk() private val mockedSplitTunneling = mockk() private val mockedServiceConnectionManager = mockk() private val mockedServiceConnectionContainer = mockk() private lateinit var testSubject: SplitTunnelingViewModel - @Before + @BeforeEach fun setup() { every { mockedSplitTunneling.enabled } returns true } - @After + @AfterEach fun tearDown() { testSubject.viewModelScope.coroutineContext.cancel() unmockkAll() } @Test - fun test_has_progress_on_start() = - runTest(testCoroutineRule.testDispatcher) { - initTestSubject(emptyList()) - val actualState: SplitTunnelingUiState = testSubject.uiState.value + fun test_has_progress_on_start() = runTest { + initTestSubject(emptyList()) + val actualState: SplitTunnelingUiState = testSubject.uiState.value - val initialExpectedState = SplitTunnelingUiState.Loading + val initialExpectedState = SplitTunnelingUiState.Loading - assertEquals(initialExpectedState, actualState) + assertEquals(initialExpectedState, actualState) - verify(exactly = 1) { mockedApplicationsProvider.getAppsList() } - } + verify(exactly = 1) { mockedApplicationsProvider.getAppsList() } + } @Test - fun test_empty_app_list() = - runTest(testCoroutineRule.testDispatcher) { - every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers - { - lambda<(Set) -> Unit>().invoke(emptySet()) - } - initTestSubject(emptyList()) - val expectedState = - SplitTunnelingUiState.ShowAppList( - excludedApps = emptyList(), - includedApps = emptyList(), - showSystemApps = false - ) - testSubject.uiState.test { assertEquals(expectedState, awaitItem()) } - } + fun test_empty_app_list() = runTest { + every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers + { + lambda<(Set) -> Unit>().invoke(emptySet()) + } + initTestSubject(emptyList()) + val expectedState = + SplitTunnelingUiState.ShowAppList( + excludedApps = emptyList(), + includedApps = emptyList(), + showSystemApps = false + ) + testSubject.uiState.test { assertEquals(expectedState, awaitItem()) } + } @Test - fun test_apps_list_delivered() = - runTest(testCoroutineRule.testDispatcher) { - val appExcluded = AppData("test.excluded", 0, "testName1") - val appNotExcluded = AppData("test.not.excluded", 0, "testName2") - every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers - { - lambda<(Set) -> Unit>().invoke(setOf(appExcluded.packageName)) - } - - initTestSubject(listOf(appExcluded, appNotExcluded)) - - val expectedState = - SplitTunnelingUiState.ShowAppList( - excludedApps = listOf(appExcluded), - includedApps = listOf(appNotExcluded), - showSystemApps = false - ) - - testSubject.uiState.test { - val actualState = awaitItem() - assertEquals(expectedState, actualState) - verifyAll { - mockedSplitTunneling.enabled - mockedSplitTunneling.excludedAppsChange = any() - } + fun test_apps_list_delivered() = runTest { + val appExcluded = AppData("test.excluded", 0, "testName1") + val appNotExcluded = AppData("test.not.excluded", 0, "testName2") + every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers + { + lambda<(Set) -> Unit>().invoke(setOf(appExcluded.packageName)) + } + + initTestSubject(listOf(appExcluded, appNotExcluded)) + + val expectedState = + SplitTunnelingUiState.ShowAppList( + excludedApps = listOf(appExcluded), + includedApps = listOf(appNotExcluded), + showSystemApps = false + ) + + testSubject.uiState.test { + val actualState = awaitItem() + assertEquals(expectedState, actualState) + verifyAll { + mockedSplitTunneling.enabled + mockedSplitTunneling.excludedAppsChange = any() } } + } @Test - fun test_include_app() = - runTest(testCoroutineRule.testDispatcher) { - var excludedAppsCallback = slot<(Set) -> Unit>() - val app = AppData("test", 0, "testName") - every { mockedSplitTunneling.includeApp(app.packageName) } just runs - every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers - { - excludedAppsCallback = lambda() - excludedAppsCallback.invoke(setOf(app.packageName)) - } - - initTestSubject(listOf(app)) - - val expectedStateBeforeAction = - SplitTunnelingUiState.ShowAppList( - excludedApps = listOf(app), - includedApps = emptyList(), - showSystemApps = false - ) - val expectedStateAfterAction = - SplitTunnelingUiState.ShowAppList( - excludedApps = emptyList(), - includedApps = listOf(app), - showSystemApps = false - ) - - testSubject.uiState.test { - assertEquals(expectedStateBeforeAction, awaitItem()) - testSubject.onIncludeAppClick(app.packageName) - excludedAppsCallback.invoke(emptySet()) - assertEquals(expectedStateAfterAction, awaitItem()) + fun test_include_app() = runTest { + var excludedAppsCallback = slot<(Set) -> Unit>() + val app = AppData("test", 0, "testName") + every { mockedSplitTunneling.includeApp(app.packageName) } just runs + every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers + { + excludedAppsCallback = lambda() + excludedAppsCallback.invoke(setOf(app.packageName)) + } - verifyAll { - mockedSplitTunneling.enabled - mockedSplitTunneling.excludedAppsChange = any() - mockedSplitTunneling.includeApp(app.packageName) - } + initTestSubject(listOf(app)) + + val expectedStateBeforeAction = + SplitTunnelingUiState.ShowAppList( + excludedApps = listOf(app), + includedApps = emptyList(), + showSystemApps = false + ) + val expectedStateAfterAction = + SplitTunnelingUiState.ShowAppList( + excludedApps = emptyList(), + includedApps = listOf(app), + showSystemApps = false + ) + + testSubject.uiState.test { + assertEquals(expectedStateBeforeAction, awaitItem()) + testSubject.onIncludeAppClick(app.packageName) + excludedAppsCallback.invoke(emptySet()) + assertEquals(expectedStateAfterAction, awaitItem()) + + verifyAll { + mockedSplitTunneling.enabled + mockedSplitTunneling.excludedAppsChange = any() + mockedSplitTunneling.includeApp(app.packageName) } } + } @Test - fun test_add_app_to_excluded() = - runTest(testCoroutineRule.testDispatcher) { - var excludedAppsCallback = slot<(Set) -> Unit>() - val app = AppData("test", 0, "testName") - every { mockedSplitTunneling.excludeApp(app.packageName) } just runs - every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers - { - excludedAppsCallback = lambda() - excludedAppsCallback.invoke(emptySet()) - } - - initTestSubject(listOf(app)) - - val expectedStateBeforeAction = - SplitTunnelingUiState.ShowAppList( - excludedApps = emptyList(), - includedApps = listOf(app), - showSystemApps = false - ) - - val expectedStateAfterAction = - SplitTunnelingUiState.ShowAppList( - excludedApps = listOf(app), - includedApps = emptyList(), - showSystemApps = false - ) - - testSubject.uiState.test { - assertEquals(expectedStateBeforeAction, awaitItem()) - testSubject.onExcludeAppClick(app.packageName) - excludedAppsCallback.invoke(setOf(app.packageName)) - assertEquals(expectedStateAfterAction, awaitItem()) + fun test_add_app_to_excluded() = runTest { + var excludedAppsCallback = slot<(Set) -> Unit>() + val app = AppData("test", 0, "testName") + every { mockedSplitTunneling.excludeApp(app.packageName) } just runs + every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers + { + excludedAppsCallback = lambda() + excludedAppsCallback.invoke(emptySet()) + } + + initTestSubject(listOf(app)) + + val expectedStateBeforeAction = + SplitTunnelingUiState.ShowAppList( + excludedApps = emptyList(), + includedApps = listOf(app), + showSystemApps = false + ) - verifyAll { - mockedSplitTunneling.enabled - mockedSplitTunneling.excludedAppsChange = any() - mockedSplitTunneling.excludeApp(app.packageName) - } + val expectedStateAfterAction = + SplitTunnelingUiState.ShowAppList( + excludedApps = listOf(app), + includedApps = emptyList(), + showSystemApps = false + ) + + testSubject.uiState.test { + assertEquals(expectedStateBeforeAction, awaitItem()) + testSubject.onExcludeAppClick(app.packageName) + excludedAppsCallback.invoke(setOf(app.packageName)) + assertEquals(expectedStateAfterAction, awaitItem()) + + verifyAll { + mockedSplitTunneling.enabled + mockedSplitTunneling.excludedAppsChange = any() + mockedSplitTunneling.excludeApp(app.packageName) } } + } private fun initTestSubject(appList: List) { every { mockedApplicationsProvider.getAppsList() } returns appList @@ -204,7 +200,7 @@ class SplitTunnelingViewModelTest { SplitTunnelingViewModel( mockedApplicationsProvider, mockedServiceConnectionManager, - testCoroutineRule.testDispatcher + UnconfinedTestDispatcher() ) } } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt index bfa068a381e3..7c54cd2c5efe 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt @@ -22,13 +22,13 @@ 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.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class VoucherDialogViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockServiceConnectionManager: ServiceConnectionManager = mockk() private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk() @@ -41,7 +41,7 @@ class VoucherDialogViewModelTest { private lateinit var viewModel: VoucherDialogViewModel - @Before + @BeforeEach fun setUp() { every { mockServiceConnectionManager.connectionState } returns serviceConnectionState @@ -52,7 +52,7 @@ class VoucherDialogViewModelTest { ) } - @After + @AfterEach fun tearDown() { unmockkAll() } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt index 0ac13777cd84..51bbe3057c4c 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt @@ -27,13 +27,13 @@ import net.mullvad.mullvadvpn.model.WireguardTunnelOptions import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.usecase.PortRangeUseCase import net.mullvad.mullvadvpn.usecase.RelayListUseCase -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class VpnSettingsViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockSettingsRepository: SettingsRepository = mockk() private val mockResources: Resources = mockk() @@ -45,7 +45,7 @@ class VpnSettingsViewModelTest { private lateinit var viewModel: VpnSettingsViewModel - @Before + @BeforeEach fun setUp() { every { mockSettingsRepository.settingsUpdates } returns mockSettingsUpdate every { mockPortRangeUseCase.portRanges() } returns portRangeFlow @@ -60,7 +60,7 @@ class VpnSettingsViewModelTest { ) } - @After + @AfterEach fun tearDown() { viewModel.viewModelScope.coroutineContext.cancel() unmockkAll() diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt index 29b0dfde7505..d0461a4ee5b0 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt @@ -37,13 +37,13 @@ import net.mullvad.mullvadvpn.usecase.PaymentUseCase import net.mullvad.talpid.util.EventNotifier import org.joda.time.DateTime import org.joda.time.ReadableInstant -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class WelcomeViewModelTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val serviceConnectionStateFlow = MutableStateFlow(ServiceConnectionState.Disconnected) @@ -68,7 +68,7 @@ class WelcomeViewModelTest { private lateinit var viewModel: WelcomeViewModel - @Before + @BeforeEach fun setUp() { mockkStatic(SERVICE_CONNECTION_MANAGER_EXTENSIONS) mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS) @@ -101,46 +101,44 @@ class WelcomeViewModelTest { ) } - @After + @AfterEach fun tearDown() { viewModel.viewModelScope.coroutineContext.cancel() unmockkAll() } @Test - fun testSitePaymentClick() = - runTest(testCoroutineRule.testDispatcher) { - // Arrange - val mockToken = "4444 5555 6666 7777" - val mockAuthTokenCache: AuthTokenCache = mockk(relaxed = true) - every { mockServiceConnectionManager.authTokenCache() } returns mockAuthTokenCache - coEvery { mockAuthTokenCache.fetchAuthToken() } returns mockToken - - // Act, Assert - viewModel.uiSideEffect.test { - viewModel.onSitePaymentClick() - val action = awaitItem() - assertIs(action) - assertEquals(mockToken, action.token) - } + fun testSitePaymentClick() = runTest { + // Arrange + val mockToken = "4444 5555 6666 7777" + val mockAuthTokenCache: AuthTokenCache = mockk(relaxed = true) + every { mockServiceConnectionManager.authTokenCache() } returns mockAuthTokenCache + coEvery { mockAuthTokenCache.fetchAuthToken() } returns mockToken + + // Act, Assert + viewModel.uiSideEffect.test { + viewModel.onSitePaymentClick() + val action = awaitItem() + assertIs(action) + assertEquals(mockToken, action.token) } + } @Test - fun testUpdateTunnelState() = - runTest(testCoroutineRule.testDispatcher) { - // Arrange - val tunnelUiStateTestItem = TunnelState.Connected(mockk(), mockk()) - - // Act, Assert - viewModel.uiState.test { - assertEquals(WelcomeUiState(), awaitItem()) - eventNotifierTunnelUiState.notify(tunnelUiStateTestItem) - serviceConnectionStateFlow.value = - ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) - val result = awaitItem() - assertEquals(tunnelUiStateTestItem, result.tunnelState) - } + fun testUpdateTunnelState() = runTest { + // Arrange + val tunnelUiStateTestItem = TunnelState.Connected(mockk(), mockk()) + + // Act, Assert + viewModel.uiState.test { + assertEquals(WelcomeUiState(), awaitItem()) + eventNotifierTunnelUiState.notify(tunnelUiStateTestItem) + serviceConnectionStateFlow.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + val result = awaitItem() + assertEquals(tunnelUiStateTestItem, result.tunnelState) } + } @Test fun testUpdateAccountNumber() = runTest { @@ -165,19 +163,18 @@ class WelcomeViewModelTest { } @Test - fun testOpenConnectScreen() = - runTest(testCoroutineRule.testDispatcher) { - // Arrange - val mockExpiryDate: DateTime = mockk() - every { mockExpiryDate.isAfter(any()) } returns true - - // Act, Assert - viewModel.uiSideEffect.test { - outOfTimeFlow.value = false - val action = awaitItem() - assertIs(action) - } + fun testOpenConnectScreen() = runTest { + // Arrange + val mockExpiryDate: DateTime = mockk() + every { mockExpiryDate.isAfter(any()) } returns true + + // Act, Assert + viewModel.uiSideEffect.test { + outOfTimeFlow.value = false + val action = awaitItem() + assertIs(action) } + } @Test fun testBillingProductsUnavailableState() = runTest { diff --git a/android/buildSrc/src/main/kotlin/Dependencies.kt b/android/buildSrc/src/main/kotlin/Dependencies.kt index 8e38491f4192..c68e6b417c42 100644 --- a/android/buildSrc/src/main/kotlin/Dependencies.kt +++ b/android/buildSrc/src/main/kotlin/Dependencies.kt @@ -2,7 +2,15 @@ object Dependencies { const val androidVolley = "com.android.volley:volley:${Versions.Android.volley}" const val commonsValidator = "commons-validator:commons-validator:${Versions.commonsValidator}" const val jodaTime = "joda-time:joda-time:${Versions.jodaTime}" - const val junit = "junit:junit:${Versions.junit}" + const val junitApi = "org.junit.jupiter:junit-jupiter-api:${Versions.junit}" + const val junitEngine = "org.junit.jupiter:junit-jupiter-engine:${Versions.junit}" + const val junitParams = "org.junit.jupiter:junit-jupiter-params:${Versions.junit}" + const val junitAndroidTestExtensions = + "de.mannodermaus.junit5:android-test-core:${Versions.Android.junit}" + const val junitAndroidTestCore = + "de.mannodermaus.junit5:android-test-extensions:${Versions.Android.junit}" + const val junitAndroidTestRunner = + "de.mannodermaus.junit5:android-test-runner:${Versions.Android.junit}" const val konsist = "com.lemonappdev:konsist:${Versions.konsist}" const val leakCanary = "com.squareup.leakcanary:leakcanary-android:${Versions.leakCanary}" const val mockkWebserver = "com.squareup.okhttp3:mockwebserver:${Versions.mockWebserver}" @@ -22,14 +30,10 @@ object Dependencies { "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.AndroidX.lifecycle}" const val espressoCore = "androidx.test.espresso:espresso-core:${Versions.AndroidX.espresso}" - const val testCore = - "androidx.test:core:${Versions.AndroidX.test}" - const val testMonitor = - "androidx.test:monitor:${Versions.AndroidX.testMonitor}" - const val testRunner = - "androidx.test:runner:${Versions.AndroidX.testRunner}" - const val testRules = - "androidx.test:rules:${Versions.AndroidX.test}" + const val testCore = "androidx.test:core:${Versions.AndroidX.test}" + const val testMonitor = "androidx.test:monitor:${Versions.AndroidX.testMonitor}" + const val testRunner = "androidx.test:runner:${Versions.AndroidX.testRunner}" + const val testRules = "androidx.test:rules:${Versions.AndroidX.test}" const val testUiAutomator = "androidx.test.uiautomator:uiautomator:${Versions.AndroidX.uiautomator}" const val testOrchestrator = @@ -37,25 +41,27 @@ object Dependencies { } object Compose { - const val destinations = "io.github.raamcosta.compose-destinations:core:${Versions.Compose.destinations}" - const val destinationsKsp = "io.github.raamcosta.compose-destinations:ksp:${Versions.Compose.destinations}" const val constrainLayout = "androidx.constraintlayout:constraintlayout-compose:${Versions.Compose.constrainLayout}" + const val destinations = + "io.github.raamcosta.compose-destinations:core:${Versions.Compose.destinations}" + const val destinationsKsp = + "io.github.raamcosta.compose-destinations:ksp:${Versions.Compose.destinations}" const val foundation = "androidx.compose.foundation:foundation:${Versions.Compose.foundation}" - const val junit = "androidx.compose.ui:ui-test-junit4:${Versions.Compose.base}" + const val junit5 = "de.mannodermaus.junit5:android-test-compose:${Versions.Android.junit}" const val material3 = "androidx.compose.material3:material3:${Versions.Compose.material3}" const val testManifest = "androidx.compose.ui:ui-test-manifest:${Versions.Compose.base}" + const val ui = "androidx.compose.ui:ui:${Versions.Compose.base}" const val uiController = "com.google.accompanist:accompanist-systemuicontroller:${Versions.Compose.uiController}" - const val ui = "androidx.compose.ui:ui:${Versions.Compose.base}" - const val uiUtil = "androidx.compose.ui:ui-util:${Versions.Compose.base}" + const val uiTestManifest = "androidx.compose.ui:ui-test-manifest:${Versions.Compose.base}" const val uiTooling = "androidx.compose.ui:ui-tooling:${Versions.Compose.base}" - const val uiToolingPreview = - "androidx.compose.ui:ui-tooling-preview:${Versions.Compose.base}" const val uiToolingAndroidPreview = "androidx.compose.ui:ui-tooling-preview-android:${Versions.Compose.base}" - + const val uiToolingPreview = + "androidx.compose.ui:ui-tooling-preview:${Versions.Compose.base}" + const val uiUtil = "androidx.compose.ui:ui-util:${Versions.Compose.base}" } object Koin { @@ -111,17 +117,18 @@ object Dependencies { const val androidApplicationId = "com.android.application" const val androidLibraryId = "com.android.library" const val androidTestId = "com.android.test" - const val playPublisher = - "com.github.triplet.gradle:play-publisher:${Versions.Plugin.playPublisher}" - const val playPublisherId = "com.github.triplet.play" - const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" - const val kotlinAndroidId = "kotlin-android" - const val kotlinParcelizeId = "kotlin-parcelize" const val dependencyCheck = "org.owasp:dependency-check-gradle:${Versions.Plugin.dependencyCheck}" const val dependencyCheckId = "org.owasp.dependencycheck" const val gradleVersionsId = "com.github.ben-manes.versions" - const val ktfmtId = "com.ncorti.ktfmt.gradle" + const val junit5 = "de.mannodermaus.android-junit5" + const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" + const val kotlinAndroidId = "kotlin-android" + const val kotlinParcelizeId = "kotlin-parcelize" const val ksp = "com.google.devtools.ksp" + const val ktfmtId = "com.ncorti.ktfmt.gradle" + const val playPublisher = + "com.github.triplet.gradle:play-publisher:${Versions.Plugin.playPublisher}" + const val playPublisherId = "com.github.triplet.play" } } diff --git a/android/buildSrc/src/main/kotlin/Versions.kt b/android/buildSrc/src/main/kotlin/Versions.kt index fede1556f7cb..dc5c5d923b22 100644 --- a/android/buildSrc/src/main/kotlin/Versions.kt +++ b/android/buildSrc/src/main/kotlin/Versions.kt @@ -1,7 +1,7 @@ object Versions { const val commonsValidator = "1.7" const val jodaTime = "2.12.5" - const val junit = "4.13.2" + const val junit = "5.10.0" const val jvmTarget = "17" const val konsist = "0.13.0" const val kotlin = "1.9.20" @@ -15,6 +15,7 @@ object Versions { object Android { const val compileSdkVersion = 34 + const val junit = "1.4.0" const val minSdkVersion = 26 const val targetSdkVersion = 34 const val volley = "1.2.1" @@ -52,6 +53,7 @@ object Versions { const val playPublisher = "3.8.4" const val dependencyCheck = "8.3.1" const val gradleVersions = "0.47.0" + const val junit5 = "1.10.0.0" const val ktfmt = "0.13.0" // Ksp version is linked with kotlin version, find matching release here: // https://github.com/google/ksp/releases diff --git a/android/gradle/verification-metadata.xml b/android/gradle/verification-metadata.xml index e485d83a533f..407f9ac4aec8 100644 --- a/android/gradle/verification-metadata.xml +++ b/android/gradle/verification-metadata.xml @@ -540,9 +540,6 @@ - - - @@ -556,9 +553,6 @@ - - - @@ -1000,6 +994,11 @@ + + + + + @@ -1959,6 +1958,11 @@ + + + + + @@ -2536,6 +2540,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3023,6 +3065,14 @@ + + + + + + + + @@ -3681,6 +3731,14 @@ + + + + + + + + @@ -3867,11 +3925,6 @@ - - - - - @@ -3885,11 +3938,21 @@ + + + + + + + + + + @@ -3925,52 +3988,99 @@ - - - + + + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + + + + + + - - - + + + - - + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3983,9 +4093,12 @@ - - - + + + + + + diff --git a/android/lib/billing/build.gradle.kts b/android/lib/billing/build.gradle.kts index 8ecc14f7cd6c..26cc345556cb 100644 --- a/android/lib/billing/build.gradle.kts +++ b/android/lib/billing/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id(Dependencies.Plugin.androidLibraryId) + id(Dependencies.Plugin.junit5) version Versions.Plugin.junit5 id(Dependencies.Plugin.kotlinAndroidId) } @@ -55,11 +56,13 @@ dependencies { implementation(project(Dependencies.Mullvad.paymentLib)) // Test dependencies + testRuntimeOnly(Dependencies.junitEngine) + testImplementation(project(Dependencies.Mullvad.commonTestLib)) testImplementation(Dependencies.Kotlin.test) testImplementation(Dependencies.KotlinX.coroutinesTest) testImplementation(Dependencies.MockK.core) - testImplementation(Dependencies.junit) + testImplementation(Dependencies.junitApi) testImplementation(Dependencies.turbine) androidTestImplementation(project(Dependencies.Mullvad.commonTestLib)) @@ -67,6 +70,7 @@ dependencies { androidTestImplementation(Dependencies.Kotlin.test) androidTestImplementation(Dependencies.KotlinX.coroutinesTest) androidTestImplementation(Dependencies.turbine) - androidTestImplementation(Dependencies.junit) + androidTestImplementation(Dependencies.junitApi) + androidTestImplementation(Dependencies.junitEngine) androidTestImplementation(Dependencies.AndroidX.espressoCore) } diff --git a/android/lib/billing/src/androidTest/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingRepositoryTest.kt b/android/lib/billing/src/androidTest/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingRepositoryTest.kt index e6f5225740a8..386932681dd1 100644 --- a/android/lib/billing/src/androidTest/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingRepositoryTest.kt +++ b/android/lib/billing/src/androidTest/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingRepositoryTest.kt @@ -34,13 +34,13 @@ import net.mullvad.mullvadvpn.lib.billing.model.BillingException import net.mullvad.mullvadvpn.lib.billing.model.PurchaseEvent import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.common.test.assertLists -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class BillingRepositoryTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockContext: Context = mockk() private lateinit var billingRepository: BillingRepository @@ -51,7 +51,7 @@ class BillingRepositoryTest { private val purchaseUpdatedListenerSlot: CapturingSlot = CapturingSlot() - @Before + @BeforeEach fun setUp() { mockkStatic(BILLING_CLIENT_CLASS) mockkStatic(BILLING_CLIENT_KOTLIN_CLASS) @@ -66,7 +66,7 @@ class BillingRepositoryTest { billingRepository = BillingRepository(mockContext) } - @After + @AfterEach fun tearDown() { unmockkAll() } diff --git a/android/lib/billing/src/test/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepositoryTest.kt b/android/lib/billing/src/test/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepositoryTest.kt index fe25457e49eb..21e13d5da8e8 100644 --- a/android/lib/billing/src/test/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepositoryTest.kt +++ b/android/lib/billing/src/test/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepositoryTest.kt @@ -25,12 +25,12 @@ import net.mullvad.mullvadvpn.model.PlayPurchaseInitError import net.mullvad.mullvadvpn.model.PlayPurchaseInitResult import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyError import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyResult -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +@ExtendWith(TestCoroutineRule::class) class BillingPaymentRepositoryTest { - @get:Rule val testCoroutineRule = TestCoroutineRule() private val mockBillingRepository: BillingRepository = mockk() private val mockPlayPurchaseRepository: PlayPurchaseRepository = mockk() @@ -39,7 +39,7 @@ class BillingPaymentRepositoryTest { private lateinit var paymentRepository: BillingPaymentRepository - @Before + @BeforeEach fun setUp() { mockkStatic(PRODUCT_DETAILS_TO_PAYMENT_PRODUCT_EXT) diff --git a/android/lib/common-test/build.gradle.kts b/android/lib/common-test/build.gradle.kts index 3d0ef6c0288d..6e3fc7c02f16 100644 --- a/android/lib/common-test/build.gradle.kts +++ b/android/lib/common-test/build.gradle.kts @@ -21,10 +21,20 @@ android { abortOnError = true warningsAsErrors = true } + + packaging { + resources { + pickFirsts += setOf( + // Fixes packaging error caused by: jetified-junit-* + "META-INF/LICENSE.md", + "META-INF/LICENSE-notice.md" + ) + } + } } dependencies { implementation(Dependencies.Kotlin.test) implementation(Dependencies.KotlinX.coroutinesTest) - implementation(Dependencies.junit) + implementation(Dependencies.junitApi) } diff --git a/android/lib/common-test/src/main/java/net/mullvad/mullvadvpn/lib/common/test/TestCoroutineRule.kt b/android/lib/common-test/src/main/java/net/mullvad/mullvadvpn/lib/common/test/TestCoroutineRule.kt index d04983be26e7..34d6739119a2 100644 --- a/android/lib/common-test/src/main/java/net/mullvad/mullvadvpn/lib/common/test/TestCoroutineRule.kt +++ b/android/lib/common-test/src/main/java/net/mullvad/mullvadvpn/lib/common/test/TestCoroutineRule.kt @@ -6,22 +6,20 @@ import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain -import org.junit.rules.TestWatcher -import org.junit.runner.Description +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext @OptIn(ExperimentalCoroutinesApi::class) class TestCoroutineRule(val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()) : - TestWatcher() { + BeforeEachCallback, AfterEachCallback { - override fun starting(description: Description) { - super.starting(description) + override fun beforeEach(var1: ExtensionContext?) { Dispatchers.setMain(testDispatcher) } - override fun finished(description: Description) { - super.finished(description) + override fun afterEach(var1: ExtensionContext?) { Dispatchers.resetMain() - // Replacement for cleanupTestCoroutines() testDispatcher.scheduler.runCurrent() } } diff --git a/android/scripts/run-instrumented-tests.sh b/android/scripts/run-instrumented-tests.sh index 5c6ace121b97..3d2548085447 100755 --- a/android/scripts/run-instrumented-tests.sh +++ b/android/scripts/run-instrumented-tests.sh @@ -122,11 +122,13 @@ if [[ "$USE_ORCHESTRATOR" == "true" ]]; then am instrument -r -w \ -e targetInstrumentation $TEST_PACKAGE/androidx.test.runner.AndroidJUnitRunner \ -e clearPackageData true \ + -e runnerBuilder de.mannodermaus.junit5.AndroidJUnit5Builder \ ${OPTIONAL_TEST_ARGUMENTS:-""} \ androidx.test.orchestrator/androidx.test.orchestrator.AndroidTestOrchestrator" else INSTRUMENTATION_COMMAND="\ am instrument -w \ + -e runnerBuilder de.mannodermaus.junit5.AndroidJUnit5Builder \ $TEST_PACKAGE/androidx.test.runner.AndroidJUnitRunner" fi adb shell "$INSTRUMENTATION_COMMAND" | tee "$INSTRUMENTATION_LOG_FILE_PATH" diff --git a/android/test/arch/build.gradle.kts b/android/test/arch/build.gradle.kts index dff039f0e2f8..18503fa36288 100644 --- a/android/test/arch/build.gradle.kts +++ b/android/test/arch/build.gradle.kts @@ -30,6 +30,6 @@ androidComponents { dependencies { testImplementation(Dependencies.Compose.uiToolingAndroidPreview) testImplementation(Dependencies.AndroidX.appcompat) - testImplementation(Dependencies.junit) + testImplementation(Dependencies.junitApi) testImplementation(Dependencies.konsist) } diff --git a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/ArchitectureTests.kt b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/ArchitectureTests.kt index 2a7e1e205e34..4c0eac135ed7 100644 --- a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/ArchitectureTests.kt +++ b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/ArchitectureTests.kt @@ -3,7 +3,7 @@ package net.mullvad.mullvadvpn.test.arch import com.lemonappdev.konsist.api.Konsist import com.lemonappdev.konsist.api.architecture.KoArchitectureCreator.assertArchitecture import com.lemonappdev.konsist.api.architecture.Layer -import org.junit.Test +import org.junit.jupiter.api.Test class ArchitectureTests { diff --git a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/GeneralTests.kt b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/GeneralTests.kt index 60842537c3af..a4049270ed8e 100644 --- a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/GeneralTests.kt +++ b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/GeneralTests.kt @@ -4,7 +4,7 @@ import com.lemonappdev.konsist.api.Konsist import com.lemonappdev.konsist.api.ext.list.properties import com.lemonappdev.konsist.api.verify.assert import com.lemonappdev.konsist.api.verify.assertNot -import org.junit.Test +import org.junit.jupiter.api.Test class GeneralTests { @Test diff --git a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/KonsistTests.kt b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/KonsistTests.kt index f2954bdb687b..a1994c4a8315 100644 --- a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/KonsistTests.kt +++ b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/KonsistTests.kt @@ -3,7 +3,7 @@ package net.mullvad.mullvadvpn.test.arch import com.lemonappdev.konsist.api.Konsist import com.lemonappdev.konsist.api.ext.list.withAnnotationOf import com.lemonappdev.konsist.api.verify.assert -import org.junit.Test +import org.junit.jupiter.api.Test class KonsistTests { @Test diff --git a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/ViewModelTests.kt b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/ViewModelTests.kt index 0f23e52a4379..d80959af781e 100644 --- a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/ViewModelTests.kt +++ b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/ViewModelTests.kt @@ -8,7 +8,7 @@ import com.lemonappdev.konsist.api.ext.list.properties import com.lemonappdev.konsist.api.ext.list.withAllParentsOf import com.lemonappdev.konsist.api.verify.assert import com.lemonappdev.konsist.api.verify.assertNot -import org.junit.Test +import org.junit.jupiter.api.Test class ViewModelTests { @Test diff --git a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/ClassTests.kt b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/ClassTests.kt index 918139bf2458..f3aa3a9938db 100644 --- a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/ClassTests.kt +++ b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/ClassTests.kt @@ -2,7 +2,7 @@ package net.mullvad.mullvadvpn.test.arch.classes import com.lemonappdev.konsist.api.Konsist import com.lemonappdev.konsist.api.verify.assert -import org.junit.Test +import org.junit.jupiter.api.Test class ClassTests { @Test diff --git a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/DataClassTests.kt b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/DataClassTests.kt index cc2f7262b128..44ca7bcd499c 100644 --- a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/DataClassTests.kt +++ b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/DataClassTests.kt @@ -4,7 +4,7 @@ import com.lemonappdev.konsist.api.Konsist import com.lemonappdev.konsist.api.ext.list.modifierprovider.withDataModifier import com.lemonappdev.konsist.api.ext.list.properties import com.lemonappdev.konsist.api.verify.assertNot -import org.junit.Test +import org.junit.jupiter.api.Test class DataClasses { @Test diff --git a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/ValueClassTests.kt b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/ValueClassTests.kt index 6b40e051e868..9d085876fd90 100644 --- a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/ValueClassTests.kt +++ b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/classes/ValueClassTests.kt @@ -2,9 +2,8 @@ package net.mullvad.mullvadvpn.test.arch.classes import com.lemonappdev.konsist.api.Konsist import com.lemonappdev.konsist.api.ext.list.modifierprovider.withValueModifier -import com.lemonappdev.konsist.api.ext.list.properties import com.lemonappdev.konsist.api.verify.assertTrue -import org.junit.Test +import org.junit.jupiter.api.Test class ValueClassTests { @Test diff --git a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/compose/ComposePreviewTests.kt b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/compose/ComposePreviewTests.kt index 5f8b1fef8026..1c2df4bdd7b7 100644 --- a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/compose/ComposePreviewTests.kt +++ b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/compose/ComposePreviewTests.kt @@ -4,7 +4,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.lemonappdev.konsist.api.Konsist import com.lemonappdev.konsist.api.ext.list.withAllAnnotationsOf import com.lemonappdev.konsist.api.verify.assert -import org.junit.Test +import org.junit.jupiter.api.Test class ComposePreviewTests { @Test diff --git a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/compose/ComposeTests.kt b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/compose/ComposeTests.kt index 025f10d11f25..2f7bb481fd04 100644 --- a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/compose/ComposeTests.kt +++ b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/compose/ComposeTests.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable import com.lemonappdev.konsist.api.Konsist import com.lemonappdev.konsist.api.ext.list.withAllAnnotationsOf import com.lemonappdev.konsist.api.verify.assert -import org.junit.Test +import org.junit.jupiter.api.Test class ComposeTests { @Test diff --git a/android/test/arch/src/test/resources/junit-platform.properties b/android/test/arch/src/test/resources/junit-platform.properties new file mode 100644 index 000000000000..c5cb4a4e8814 --- /dev/null +++ b/android/test/arch/src/test/resources/junit-platform.properties @@ -0,0 +1,6 @@ +# Enables parallel execution of Konsist tests, values found here: +# https://docs.konsist.lemonappdev.com/advanced/additional-junit5-setup +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.mode.default=concurrent +junit.jupiter.execution.parallel.config.strategy=dynamic +junit.jupiter.execution.parallel.config.dynamic.factor=0.95 diff --git a/android/test/common/build.gradle.kts b/android/test/common/build.gradle.kts index 99fcb9d2a7c1..df9dea41ab71 100644 --- a/android/test/common/build.gradle.kts +++ b/android/test/common/build.gradle.kts @@ -22,6 +22,16 @@ android { abortOnError = true warningsAsErrors = true } + + packaging { + resources { + pickFirsts += setOf( + // Fixes packaging error caused by: jetified-junit-* + "META-INF/LICENSE.md", + "META-INF/LICENSE-notice.md" + ) + } + } } androidComponents { @@ -37,7 +47,7 @@ dependencies { implementation(Dependencies.AndroidX.testRunner) implementation(Dependencies.AndroidX.testRules) implementation(Dependencies.AndroidX.testUiAutomator) - implementation(Dependencies.junit) + implementation(Dependencies.junitEngine) implementation(Dependencies.Kotlin.stdlib) androidTestUtil(Dependencies.AndroidX.testOrchestrator) diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt index 024522e94a64..9138982a432a 100644 --- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt @@ -16,15 +16,15 @@ import java.io.IOException import java.nio.file.Paths import java.time.OffsetDateTime import java.time.temporal.ChronoUnit -import org.junit.rules.TestWatcher -import org.junit.runner.Description +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.TestWatcher -class CaptureScreenshotOnFailedTestRule(private val testTag: String) : TestWatcher() { +class CaptureScreenshotOnFailedTestRule(private val testTag: String) : TestWatcher { - override fun failed(e: Throwable?, description: Description) { - Log.d(testTag, "Capturing screenshot of failed test: " + description.methodName) + override fun testFailed(context: ExtensionContext, cause: Throwable) { + Log.d(testTag, "Capturing screenshot of failed test: " + context.requiredTestMethod.name) val timestamp = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS) - val screenshotName = "$timestamp-${description.methodName}.jpeg" + val screenshotName = "$timestamp-${context.requiredTestMethod.name}.jpeg" captureScreenshot(testTag, screenshotName) } diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/ForgetAllVpnAppsInSettingsTestRule.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/ForgetAllVpnAppsInSettingsTestRule.kt index eebdb291abb1..0e5371fcc38f 100644 --- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/ForgetAllVpnAppsInSettingsTestRule.kt +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/ForgetAllVpnAppsInSettingsTestRule.kt @@ -7,11 +7,11 @@ import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import net.mullvad.mullvadvpn.test.common.extension.findObjectByCaseInsensitiveText import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout -import org.junit.rules.TestWatcher -import org.junit.runner.Description +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback +import org.junit.jupiter.api.extension.ExtensionContext -class ForgetAllVpnAppsInSettingsTestRule : TestWatcher() { - override fun starting(description: Description) { +class ForgetAllVpnAppsInSettingsTestRule : BeforeTestExecutionCallback { + override fun beforeTestExecution(context: ExtensionContext) { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) val targetContext = InstrumentationRegistry.getInstrumentation().targetContext targetContext.startActivity( diff --git a/android/test/e2e/build.gradle.kts b/android/test/e2e/build.gradle.kts index 8e24974fcd93..946e8effa969 100644 --- a/android/test/e2e/build.gradle.kts +++ b/android/test/e2e/build.gradle.kts @@ -4,6 +4,7 @@ import java.util.Properties plugins { id(Dependencies.Plugin.androidTestId) id(Dependencies.Plugin.kotlinAndroidId) + id(Dependencies.Plugin.junit5) version Versions.Plugin.junit5 } android { @@ -14,6 +15,8 @@ android { minSdk = Versions.Android.minSdkVersion testApplicationId = "net.mullvad.mullvadvpn.test.e2e" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["runnerBuilder"] = + "de.mannodermaus.junit5.AndroidJUnit5Builder" targetProjectPath = ":app" missingDimensionStrategy(FlavorDimensions.BILLING, Flavors.OSS) @@ -62,6 +65,17 @@ android { abortOnError = true warningsAsErrors = true } + + packaging { + resources { + pickFirsts += + setOf( + // Fixes packaging error caused by: jetified-junit-* + "META-INF/LICENSE.md", + "META-INF/LICENSE-notice.md" + ) + } + } } configure { @@ -83,6 +97,10 @@ dependencies { implementation(Dependencies.AndroidX.testRules) implementation(Dependencies.AndroidX.testUiAutomator) implementation(Dependencies.androidVolley) + implementation(Dependencies.junitAndroidTestExtensions) + implementation(Dependencies.junitApi) + implementation(Dependencies.junitAndroidTestCore) + implementation(Dependencies.junitAndroidTestRunner) implementation(Dependencies.Kotlin.stdlib) androidTestUtil(Dependencies.AndroidX.testOrchestrator) diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt index 488162b08cc1..bbecb037f359 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt @@ -1,20 +1,22 @@ package net.mullvad.mullvadvpn.test.e2e import androidx.test.uiautomator.By -import junit.framework.Assert.assertEquals import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout import net.mullvad.mullvadvpn.test.common.rule.ForgetAllVpnAppsInSettingsTestRule import net.mullvad.mullvadvpn.test.e2e.misc.CleanupAccountTestRule import net.mullvad.mullvadvpn.test.e2e.misc.ConnCheckState import net.mullvad.mullvadvpn.test.e2e.misc.SimpleMullvadHttpClient -import org.junit.Rule -import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension class ConnectionTest : EndToEndTest() { - @Rule @JvmField val cleanupAccountTestRule = CleanupAccountTestRule() + @RegisterExtension @JvmField val cleanupAccountTestRule = CleanupAccountTestRule() - @Rule @JvmField val forgetAllVpnAppsInSettingsTestRule = ForgetAllVpnAppsInSettingsTestRule() + @RegisterExtension + @JvmField + val forgetAllVpnAppsInSettingsTestRule = ForgetAllVpnAppsInSettingsTestRule() @Test fun testConnectAndVerifyWithConnectionCheck() { diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt index c9226993b89d..2cf8ba712d69 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt @@ -4,35 +4,32 @@ import android.Manifest import android.content.Context import android.os.Build import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.rule.GrantPermissionRule -import androidx.test.runner.AndroidJUnit4 import androidx.test.uiautomator.UiDevice +import de.mannodermaus.junit5.extensions.GrantPermissionExtension import net.mullvad.mullvadvpn.test.common.interactor.AppInteractor import net.mullvad.mullvadvpn.test.common.rule.CaptureScreenshotOnFailedTestRule import net.mullvad.mullvadvpn.test.e2e.constant.INVALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY import net.mullvad.mullvadvpn.test.e2e.constant.LOG_TAG import net.mullvad.mullvadvpn.test.e2e.constant.VALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY import net.mullvad.mullvadvpn.test.e2e.extension.getRequiredArgument -import org.junit.Before -import org.junit.Rule -import org.junit.runner.RunWith +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.extension.RegisterExtension -@RunWith(AndroidJUnit4::class) abstract class EndToEndTest { - @Rule @JvmField val rule = CaptureScreenshotOnFailedTestRule(LOG_TAG) + @RegisterExtension @JvmField val rule = CaptureScreenshotOnFailedTestRule(LOG_TAG) - @Rule @JvmField - val permissionRule: GrantPermissionRule? = - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - GrantPermissionRule.grant( + @RegisterExtension + val extension = + (if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + GrantPermissionExtension.grant( Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE ) } else { - null - } + GrantPermissionExtension.grant() + }) lateinit var device: UiDevice lateinit var targetContext: Context @@ -40,7 +37,7 @@ abstract class EndToEndTest { lateinit var validTestAccountToken: String lateinit var invalidTestAccountToken: String - @Before + @BeforeEach fun setup() { device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) targetContext = InstrumentationRegistry.getInstrumentation().targetContext diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LaunchAppTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LaunchAppTest.kt index 64f534990c3b..f68df9285489 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LaunchAppTest.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LaunchAppTest.kt @@ -1,10 +1,7 @@ package net.mullvad.mullvadvpn.test.e2e -import androidx.test.runner.AndroidJUnit4 -import org.junit.Test -import org.junit.runner.RunWith +import org.junit.jupiter.api.Test -@RunWith(AndroidJUnit4::class) class LaunchAppTest : EndToEndTest() { @Test fun testLaunchApp() { diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LoginTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LoginTest.kt index ab3ef54fb100..792c63f1a1bb 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LoginTest.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LoginTest.kt @@ -1,20 +1,17 @@ package net.mullvad.mullvadvpn.test.e2e -import androidx.test.runner.AndroidJUnit4 import androidx.test.uiautomator.By import net.mullvad.mullvadvpn.test.common.constant.LOGIN_FAILURE_TIMEOUT import net.mullvad.mullvadvpn.test.common.extension.clickAgreeOnPrivacyDisclaimer import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPermissionPromptIfApiLevel33AndAbove import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout import net.mullvad.mullvadvpn.test.e2e.misc.CleanupAccountTestRule -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension -@RunWith(AndroidJUnit4::class) class LoginTest : EndToEndTest() { - @Rule @JvmField val cleanupAccountTestRule = CleanupAccountTestRule() + @RegisterExtension @JvmField val cleanupAccountTestRule = CleanupAccountTestRule() @Test fun testLoginWithInvalidCredentials() { diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt index ac3997c3722c..5e72305efe2b 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt @@ -5,7 +5,7 @@ import net.mullvad.mullvadvpn.test.common.constant.WEB_TIMEOUT import net.mullvad.mullvadvpn.test.common.extension.clickAgreeOnPrivacyDisclaimer import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPermissionPromptIfApiLevel33AndAbove import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout -import org.junit.Test +import org.junit.jupiter.api.Test class WebLinkTest : EndToEndTest() { @Test diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/CleanupAccountTestRule.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/CleanupAccountTestRule.kt index 2e19cb42fe7d..ba42862397b3 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/CleanupAccountTestRule.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/CleanupAccountTestRule.kt @@ -6,12 +6,13 @@ import net.mullvad.mullvadvpn.test.e2e.constant.LOG_TAG import net.mullvad.mullvadvpn.test.e2e.constant.VALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY import net.mullvad.mullvadvpn.test.e2e.extension.getRequiredArgument import net.mullvad.mullvadvpn.test.e2e.interactor.MullvadAccountInteractor -import org.junit.rules.TestWatcher -import org.junit.runner.Description +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext -class CleanupAccountTestRule : TestWatcher() { - override fun starting(description: Description) { - Log.d(LOG_TAG, "Cleaning up account before test: ${description.methodName}") +class CleanupAccountTestRule : BeforeEachCallback { + + override fun beforeEach(context: ExtensionContext) { + Log.d(LOG_TAG, "Cleaning up account before test: ${context.requiredTestMethod.name}") val targetContext = InstrumentationRegistry.getInstrumentation().targetContext val validTestAccountToken = InstrumentationRegistry.getArguments() diff --git a/android/test/firebase-test-lab.yml b/android/test/firebase-test-lab.yml index feaefbc236b9..d84e64134b5a 100644 --- a/android/test/firebase-test-lab.yml +++ b/android/test/firebase-test-lab.yml @@ -12,3 +12,4 @@ default: - {model: GoogleTvEmulator, version: 30, locale: en, orientation: landscape} environment-variables: clearPackageData: "true" + runnerBuilder: "de.mannodermaus.junit5.AndroidJUnit5Builder" diff --git a/android/test/mockapi/build.gradle.kts b/android/test/mockapi/build.gradle.kts index 8be38fdcbc8e..e5d20d3d5051 100644 --- a/android/test/mockapi/build.gradle.kts +++ b/android/test/mockapi/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id(Dependencies.Plugin.androidTestId) id(Dependencies.Plugin.kotlinAndroidId) + id(Dependencies.Plugin.junit5) version Versions.Plugin.junit5 } android { @@ -11,6 +12,8 @@ android { minSdk = Versions.Android.minSdkVersion testApplicationId = "net.mullvad.mullvadvpn.test.mockapi" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["runnerBuilder"] = + "de.mannodermaus.junit5.AndroidJUnit5Builder" targetProjectPath = ":app" missingDimensionStrategy(FlavorDimensions.BILLING, Flavors.OSS) @@ -37,6 +40,16 @@ android { abortOnError = true warningsAsErrors = true } + + packaging { + resources { + pickFirsts += setOf( + // Fixes packaging error caused by: jetified-junit-* + "META-INF/LICENSE.md", + "META-INF/LICENSE-notice.md" + ) + } + } } configure { @@ -59,6 +72,10 @@ dependencies { implementation(Dependencies.AndroidX.testRules) implementation(Dependencies.AndroidX.testUiAutomator) implementation(Dependencies.jodaTime) + implementation(Dependencies.junitAndroidTestExtensions) + implementation(Dependencies.junitApi) + implementation(Dependencies.junitAndroidTestCore) + implementation(Dependencies.junitAndroidTestRunner) implementation(Dependencies.Kotlin.stdlib) implementation(Dependencies.mockkWebserver) diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt index 7d94b37492fc..3f3d88283544 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt @@ -1,6 +1,5 @@ package net.mullvad.mullvadvpn.test.mockapi -import androidx.test.runner.AndroidJUnit4 import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import net.mullvad.mullvadvpn.compose.test.LOGIN_TITLE_TEST_TAG @@ -10,11 +9,9 @@ import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPerm import net.mullvad.mullvadvpn.test.common.extension.dismissChangelogDialogIfShown import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout import net.mullvad.mullvadvpn.test.mockapi.util.currentUtcTimeWithOffsetZero -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test -@RunWith(AndroidJUnit4::class) class LoginMockApiTest : MockApiTest() { @Test fun testLoginWithInvalidCredentials() { diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LogoutMockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LogoutMockApiTest.kt index e13a72829677..266fe73977d4 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LogoutMockApiTest.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LogoutMockApiTest.kt @@ -1,17 +1,14 @@ package net.mullvad.mullvadvpn.test.mockapi -import androidx.test.runner.AndroidJUnit4 import androidx.test.uiautomator.By -import junit.framework.TestCase.assertNotNull import net.mullvad.mullvadvpn.test.common.extension.clickAgreeOnPrivacyDisclaimer import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPermissionPromptIfApiLevel33AndAbove import net.mullvad.mullvadvpn.test.common.extension.dismissChangelogDialogIfShown import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout import net.mullvad.mullvadvpn.test.mockapi.util.currentUtcTimeWithOffsetZero -import org.junit.Test -import org.junit.runner.RunWith +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test -@RunWith(AndroidJUnit4::class) class LogoutMockApiTest : MockApiTest() { @Test diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt index 702aa72db46b..9ee4f52e04d9 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt @@ -5,29 +5,26 @@ import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.content.Context import android.util.Log import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.rule.GrantPermissionRule -import androidx.test.runner.AndroidJUnit4 import androidx.test.uiautomator.UiDevice +import de.mannodermaus.junit5.extensions.GrantPermissionExtension import java.net.InetAddress import net.mullvad.mullvadvpn.lib.endpoint.CustomApiEndpointConfiguration import net.mullvad.mullvadvpn.test.common.interactor.AppInteractor import net.mullvad.mullvadvpn.test.common.rule.CaptureScreenshotOnFailedTestRule import net.mullvad.mullvadvpn.test.mockapi.constant.LOG_TAG import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.runner.RunWith +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.extension.RegisterExtension -@RunWith(AndroidJUnit4::class) abstract class MockApiTest { - @Rule @JvmField val rule = CaptureScreenshotOnFailedTestRule(LOG_TAG) + @RegisterExtension @JvmField val rule = CaptureScreenshotOnFailedTestRule(LOG_TAG) - @Rule + @RegisterExtension @JvmField - val permissionRule: GrantPermissionRule = - GrantPermissionRule.grant(WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionExtension = + GrantPermissionExtension.grant(WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE) protected val apiDispatcher = MockApiDispatcher() private val mockWebServer = MockWebServer().apply { dispatcher = apiDispatcher } @@ -37,7 +34,7 @@ abstract class MockApiTest { lateinit var app: AppInteractor lateinit var endpoint: CustomApiEndpointConfiguration - @Before + @BeforeEach open fun setup() { device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) targetContext = InstrumentationRegistry.getInstrumentation().targetContext @@ -49,7 +46,7 @@ abstract class MockApiTest { endpoint = createEndpoint(mockWebServer.port) } - @After + @AfterEach open fun teardown() { mockWebServer.shutdown() }