Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate tests to JUnit5 #5601

Merged
merged 8 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/android-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 9 additions & 3 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
@@ -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() } }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 =
Expand Down
Loading
Loading