diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2023-05-12 13:04:51 +0200 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2023-05-23 16:27:32 +0200 |
| commit | a4f34db77fbe413f6c1c6eae396608571c24ca06 (patch) | |
| tree | 503a9619f7299a5ba8765c57cbeb6c916c8c2565 /android/app/src | |
| parent | 893b8bd55ac6d926a4eda240956530677782aa3b (diff) | |
| download | mullvadvpn-a4f34db77fbe413f6c1c6eae396608571c24ca06.tar.xz mullvadvpn-a4f34db77fbe413f6c1c6eae396608571c24ca06.zip | |
Migrate Split tunneling instrumental tests
Remove SplitTunnelingFragmentTest
Add SplitTunnelingScreenTest
Diffstat (limited to 'android/app/src')
2 files changed, 220 insertions, 164 deletions
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 new file mode 100644 index 0000000000..087c4b72a7 --- /dev/null +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreenTest.kt @@ -0,0 +1,220 @@ +package net.mullvad.mullvadvpn.compose.screen + +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import io.mockk.verify +import net.mullvad.mullvadvpn.applist.AppData +import net.mullvad.mullvadvpn.applist.ApplicationsIconManager +import net.mullvad.mullvadvpn.compose.state.SplitTunnelingUiState +import net.mullvad.mullvadvpn.di.APPS_SCOPE +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.koin.core.context.loadKoinModules +import org.koin.core.context.unloadKoinModules +import org.koin.core.qualifier.named +import org.koin.core.scope.Scope +import org.koin.dsl.module +import org.koin.java.KoinJavaComponent.getKoin + +class SplitTunnelingScreenTest { + @get:Rule val composeTestRule = createComposeRule() + private lateinit var scope: Scope + + private val testModule = module { + scope(named(APPS_SCOPE)) { + scoped { + mockk<ApplicationsIconManager>().apply { + every { getAppIcon(any()) } returns mockk(relaxed = true) + } + } + } + } + + @Before + fun setup() { + MockKAnnotations.init(this) + loadKoinModules(testModule) + scope = getKoin().getOrCreateScope(APPS_SCOPE, named(APPS_SCOPE)) + } + + @After + fun tearDown() { + scope.close() + unloadKoinModules(testModule) + unmockkAll() + } + + @Test + fun testLoadingState() { + // Arrange + composeTestRule.setContent { SplitTunnelingScreen(uiState = SplitTunnelingUiState.Loading) } + + // Assert + composeTestRule.apply { + 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.setContent { + SplitTunnelingScreen( + uiState = + SplitTunnelingUiState.Data( + excludedApps = listOf(excludedApp), + includedApps = listOf(includedApp), + showSystemApps = false + ) + ) + } + + // Assert + composeTestRule.apply { + onNodeWithText(TITLE).assertExists() + onNodeWithText(DESCRIPTION).assertExists() + onNodeWithText(EXCLUDED_APPLICATIONS).assertExists() + onNodeWithText(EXCLUDED_APP_NAME).assertExists() + onNodeWithText(SHOW_SYSTEM_APPS).assertExists() + 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.setContent { + SplitTunnelingScreen( + uiState = + SplitTunnelingUiState.Data( + excludedApps = emptyList(), + includedApps = listOf(includedApp), + showSystemApps = false + ) + ) + } + + // Assert + composeTestRule.apply { + onNodeWithText(TITLE).assertExists() + onNodeWithText(DESCRIPTION).assertExists() + onNodeWithText(EXCLUDED_APPLICATIONS).assertDoesNotExist() + onNodeWithText(EXCLUDED_APP_NAME).assertDoesNotExist() + onNodeWithText(SHOW_SYSTEM_APPS).assertExists() + 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.setContent { + SplitTunnelingScreen( + uiState = + SplitTunnelingUiState.Data( + excludedApps = listOf(excludedApp), + includedApps = listOf(includedApp), + showSystemApps = false + ), + addToExcluded = mockedClickHandler + ) + } + + // Act + composeTestRule.onNodeWithText(INCLUDED_APP_NAME).performClick() + + // 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.setContent { + SplitTunnelingScreen( + uiState = + SplitTunnelingUiState.Data( + excludedApps = listOf(excludedApp), + includedApps = listOf(includedApp), + showSystemApps = false + ), + removeFromExcluded = mockedClickHandler + ) + } + + // Act + composeTestRule.onNodeWithText(EXCLUDED_APP_NAME).performClick() + + // 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.setContent { + SplitTunnelingScreen( + uiState = + SplitTunnelingUiState.Data( + excludedApps = listOf(excludedApp), + includedApps = listOf(includedApp), + showSystemApps = false + ), + onShowSystemAppsClicked = mockedClickHandler + ) + } + + // Act + composeTestRule.onNodeWithText(SHOW_SYSTEM_APPS).performClick() + + // Assert + verify { mockedClickHandler.invoke(true) } + } + + companion object { + private const val EXCLUDED_APP_PACKAGE_NAME = "excluded-pkg" + private const val EXCLUDED_APP_NAME = "Excluded Name" + private const val INCLUDED_APP_PACKAGE_NAME = "included-pkg" + private const val INCLUDED_APP_NAME = "Included Name" + private const val TITLE = "Split tunneling" + private const val DESCRIPTION = + "Split tunneling makes it possible to select which applications should not be routed through the VPN tunnel." + private const val EXCLUDED_APPLICATIONS = "Excluded applications" + private const val SHOW_SYSTEM_APPS = "Show system apps" + private const val ALL_APPLICATIONS = "All applications" + } +} diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/ui/fragment/SplitTunnelingFragmentTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/ui/fragment/SplitTunnelingFragmentTest.kt deleted file mode 100644 index f0b6d0a24d..0000000000 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/ui/fragment/SplitTunnelingFragmentTest.kt +++ /dev/null @@ -1,164 +0,0 @@ -package net.mullvad.mullvadvpn.ui.fragment - -import androidx.fragment.app.testing.launchFragmentInContainer -import androidx.lifecycle.Lifecycle -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers.withContentDescription -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withText -import androidx.test.filters.LargeTest -import androidx.test.runner.AndroidJUnit4 -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerifyAll -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.mockkClass -import io.mockk.unmockkAll -import io.mockk.verifyAll -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.runBlocking -import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.RecyclerViewMatcher.Companion.withRecyclerView -import net.mullvad.mullvadvpn.applist.ApplicationsIconManager -import net.mullvad.mullvadvpn.applist.ViewIntent -import net.mullvad.mullvadvpn.di.APPS_SCOPE -import net.mullvad.mullvadvpn.model.ListItemData -import net.mullvad.mullvadvpn.model.WidgetState -import net.mullvad.mullvadvpn.viewmodel.SplitTunnelingViewModel -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.koin.core.context.loadKoinModules -import org.koin.core.context.unloadKoinModules -import org.koin.core.qualifier.named -import org.koin.core.scope.Scope -import org.koin.dsl.module -import org.koin.test.KoinTest -import org.koin.test.mock.MockProviderRule -import org.koin.test.mock.declareMock - -@RunWith(AndroidJUnit4::class) -@LargeTest -class SplitTunnelingFragmentTest : KoinTest { - - private val mockedViewModel = mockk<SplitTunnelingViewModel>(relaxUnitFun = true) - private val sharedFlow = MutableSharedFlow<List<ListItemData>>() - private lateinit var scope: Scope - - private val testModule = module { - scope(named(APPS_SCOPE)) { - scoped { - mockk<ApplicationsIconManager>().apply { - every { getAppIcon(any()) } returns mockk(relaxed = true) - } - } - } - } - - @get:Rule - val mockProvider = - MockProviderRule.create { clazz -> - when (clazz) { - SplitTunnelingViewModel::class -> mockedViewModel - else -> mockkClass(clazz) - } - } - - @Before - fun setUp() { - loadKoinModules(testModule) - scope = getKoin().getOrCreateScope(APPS_SCOPE, named(APPS_SCOPE)) - scope.declareMock<SplitTunnelingViewModel>() - every { mockedViewModel.listItems } returns sharedFlow - coEvery { mockedViewModel.processIntent(ViewIntent.ViewIsReady) } just Runs - } - - @After - fun tearDown() { - scope.close() - unloadKoinModules(testModule) - unmockkAll() - } - - @Test - fun test_fragment_title() { - launchFragmentInContainer<SplitTunnelingFragment>(themeResId = R.style.AppTheme) - - onView(withId(R.id.collapsing_toolbar)) - .check(matches(withContentDescription("Split tunneling"))) - } - - @Test - fun test_fragment_loading() { - val scenario = - launchFragmentInContainer<SplitTunnelingFragment>( - themeResId = R.style.AppTheme, - initialState = Lifecycle.State.CREATED - ) - scenario.moveToState(Lifecycle.State.RESUMED) - sharedFlow.tryEmit(emptyList()) - - verifyAll { mockedViewModel.listItems } - } - - @Test - fun test_fragment_list_displayed() = runBlocking { - launchFragmentInContainer<SplitTunnelingFragment>( - themeResId = R.style.AppTheme, - initialState = Lifecycle.State.RESUMED - ) - - sharedFlow.emit( - listOf( - ListItemData.build("testItem") { - type = ListItemData.PLAIN - text = "Test Item" - action = ListItemData.ItemAction(text.toString()) - } - ) - ) - - onView(withRecyclerView(R.id.recyclerView).atPositionOnView(0, R.id.plain_text)) - .check(matches(withText("Test Item"))) - - verifyAll { mockedViewModel.listItems } - } - - @Test - fun test_fragment_list_click_application_item() = runBlocking { - val testListItem = - ListItemData.build("test.package.name") { - type = ListItemData.APPLICATION - text = "Test App Name" - action = ListItemData.ItemAction("test.package.name") - widget = WidgetState.ImageState(R.drawable.ic_icons_add) - } - - coEvery { - mockedViewModel.processIntent(ViewIntent.ChangeApplicationGroup(testListItem)) - } just Runs - - launchFragmentInContainer<SplitTunnelingFragment>( - themeResId = R.style.AppTheme, - initialState = Lifecycle.State.RESUMED - ) - - sharedFlow.emit(listOf(testListItem)) - - onView(withRecyclerView(R.id.recyclerView).atPositionOnView(0, R.id.itemText)) - .check(matches(withText("Test App Name"))) - - onView(withRecyclerView(R.id.recyclerView).atPositionOnView(0)).perform(click()) - - coVerifyAll { - mockedViewModel.listItems - mockedViewModel.processIntent(ViewIntent.ChangeApplicationGroup(testListItem)) - } - } -} |
