diff options
| author | Aleksandr Granin <aleksandr@mullvad.net> | 2021-03-29 16:31:31 +0200 |
|---|---|---|
| committer | Aleksandr Granin <aleksandr@mullvad.net> | 2021-04-01 13:30:37 +0200 |
| commit | 99a40bc34d559cbb0bf936ad510b97095a02d062 (patch) | |
| tree | d40629748ec2fe8e643588eea62f58392623f4f6 /android/src/test | |
| parent | a487f5c04431013b884e1e5734370dc22e289279 (diff) | |
| download | mullvadvpn-99a40bc34d559cbb0bf936ad510b97095a02d062.tar.xz mullvadvpn-99a40bc34d559cbb0bf936ad510b97095a02d062.zip | |
Create SplitTunneling ViewModel and tests
Diffstat (limited to 'android/src/test')
5 files changed, 270 insertions, 8 deletions
diff --git a/android/src/test/kotlin/net/mullvad/mullvadvpn/TestCoroutineRule.kt b/android/src/test/kotlin/net/mullvad/mullvadvpn/TestCoroutineRule.kt new file mode 100644 index 0000000000..1acdf9e577 --- /dev/null +++ b/android/src/test/kotlin/net/mullvad/mullvadvpn/TestCoroutineRule.kt @@ -0,0 +1,24 @@ +package net.mullvad.mullvadvpn + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +class TestCoroutineRule( + val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() +) : TestWatcher() { + + override fun starting(description: Description?) { + super.starting(description) + Dispatchers.setMain(testDispatcher) + } + + override fun finished(description: Description?) { + super.finished(description) + Dispatchers.resetMain() + testDispatcher.cleanupTestCoroutines() + } +} diff --git a/android/src/test/kotlin/net/mullvad/mullvadvpn/TestUtils.kt b/android/src/test/kotlin/net/mullvad/mullvadvpn/TestUtils.kt new file mode 100644 index 0000000000..4c4f043c06 --- /dev/null +++ b/android/src/test/kotlin/net/mullvad/mullvadvpn/TestUtils.kt @@ -0,0 +1,11 @@ +package net.mullvad.mullvadvpn + +import kotlin.test.assertTrue + +fun <T> assertLists(expected: List<T>, actual: List<T>, message: String? = null) = assertTrue( + expected.size == actual.size && expected.containsAll(actual) && actual.containsAll(expected), + message ?: """Expected list should have same size and contains same items. + | Expected(${expected.size}): $expected + | Actual(${actual.size}) : $actual + """.trimMargin() +) diff --git a/android/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt b/android/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt index 6f871a28ee..e6d43621a1 100644 --- a/android/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt +++ b/android/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt @@ -86,7 +86,7 @@ class ApplicationsIconManagerTest { } @Test - fun throw_exception_when_invoke_from_MainThread() { + fun test_throw_exception_when_invoke_from_MainThread() { val testPackageName = "test" every { mockedMainLooper.isCurrentThread } returns true diff --git a/android/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt b/android/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt index cb24158422..21ea736ee3 100644 --- a/android/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt +++ b/android/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt @@ -7,6 +7,8 @@ import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll import io.mockk.verifyAll +import kotlinx.coroutines.test.runBlockingTest +import net.mullvad.mullvadvpn.assertLists import org.junit.After import org.junit.Test @@ -22,7 +24,7 @@ class ApplicationsProviderTest { } @Test - fun test_get_apps() { + fun test_get_apps() = runBlockingTest { val launchWithInternetPackageName = "launch_with_internet_package_name" val launchWithoutInternetPackageName = "launch_without_internet_package_name" val nonLaunchWithInternetPackageName = "non_launch_with_internet_package_name" @@ -38,15 +40,12 @@ class ApplicationsProviderTest { createApplicationInfo(selfPackageName, internet = true, launch = true) ) - val result = testSubject.getAppsList() + val result = testSubject.getAppsListAsync().await() val expected = listOf( AppData(launchWithInternetPackageName, 0, launchWithInternetPackageName) ) - assert( - expected.size == result.size && - expected.containsAll(result) && - result.containsAll(expected) - ) + + assertLists(expected, result) verifyAll { mockedPackageManager.getInstalledApplications(PackageManager.GET_META_DATA) diff --git a/android/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt b/android/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt new file mode 100644 index 0000000000..c0e447e5a0 --- /dev/null +++ b/android/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt @@ -0,0 +1,228 @@ +package net.mullvad.mullvadvpn.viewmodel + +import androidx.annotation.StringRes +import androidx.lifecycle.viewModelScope +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.unmockkAll +import io.mockk.verify +import io.mockk.verifyAll +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.async +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.TestCoroutineRule +import net.mullvad.mullvadvpn.applist.AppData +import net.mullvad.mullvadvpn.applist.ApplicationsProvider +import net.mullvad.mullvadvpn.applist.ViewIntent +import net.mullvad.mullvadvpn.assertLists +import net.mullvad.mullvadvpn.model.ListItemData +import net.mullvad.mullvadvpn.model.WidgetState +import net.mullvad.mullvadvpn.service.SplitTunneling +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class SplitTunnelingViewModelTest { + @get:Rule + val testCoroutineRule = TestCoroutineRule() + private val mockedApplicationsProvider = mockk<ApplicationsProvider>() + private val mockedSplitTunneling = mockk<SplitTunneling>() + private val appsProviderDeferred = CompletableDeferred<List<AppData>>() + private lateinit var testSubject: SplitTunnelingViewModel + + @Before + fun setup() { + every { mockedSplitTunneling.enabled } returns true + coEvery { mockedApplicationsProvider.getAppsListAsync() } returns appsProviderDeferred + testSubject = SplitTunnelingViewModel( + mockedApplicationsProvider, + mockedSplitTunneling + ) + Thread.sleep(50) + } + + @After + fun tearDown() { + testSubject.viewModelScope.coroutineContext.cancel() + unmockkAll() + } + + @Test + fun test_has_progress_on_start() = runBlocking { + val actualList: List<ListItemData> = testSubject.listItems.first() + val initialExpectedList = listOf( + createTextItem(R.string.split_tunneling_description), + createDivider(0), createProgressItem() + ) + + assertLists(initialExpectedList, actualList) + + verify(exactly = 1) { + mockedApplicationsProvider.getAppsListAsync() + } + } + + @Test + fun test_empty_app_list() = runBlocking { + val flow = testSubject.listItems + async { + testSubject.processIntent(ViewIntent.ViewIsReady) + appsProviderDeferred.complete(emptyList()) + } + val actualList = flow.drop(1).first() + val expectedList = listOf(createTextItem(R.string.split_tunneling_description)) + assertLists(expectedList, actualList) + } + + @Test + fun test_apps_list_delivered() = runBlocking { + val appExcluded = AppData("test.excluded", 0, "testName1") + val appNotExcluded = AppData("test.not.excluded", 0, "testName2") + every { mockedSplitTunneling.excludedAppList } returns listOf(appExcluded.packageName) + + testSubject.processIntent(ViewIntent.ViewIsReady) + appsProviderDeferred.complete(listOf(appExcluded, appNotExcluded)) + + val actualList = testSubject.listItems.drop(1).first() + val expectedList = listOf( + createTextItem(R.string.split_tunneling_description), + createDivider(0), + createMainItem(R.string.exclude_applications), + createApplicationItem(appExcluded, true), + createDivider(1), + createMainItem(R.string.all_applications), + createApplicationItem(appNotExcluded, false), + ) + + assertLists(expectedList, actualList) + verifyAll { + mockedSplitTunneling.enabled + mockedSplitTunneling.excludedAppList + mockedSplitTunneling.excludedAppList + } + } + + @Test + fun test_remove_app_from_excluded() = runBlocking { + val flow = testSubject.listItems.drop(1) + val app = AppData("test", 0, "testName") + every { mockedSplitTunneling.excludedAppList } returns listOf(app.packageName) + every { mockedSplitTunneling.includeApp(app.packageName) } just Runs + async { + testSubject.processIntent(ViewIntent.ViewIsReady) + appsProviderDeferred.complete(listOf(app)) + } + + val listBeforeAction = flow.first() + val expectedListBeforeAction = listOf( + createTextItem(R.string.split_tunneling_description), + createDivider(0), + createMainItem(R.string.exclude_applications), + createApplicationItem(app, true), + ) + + assertLists(expectedListBeforeAction, listBeforeAction) + + val item = listBeforeAction.first { it.identifier == app.packageName } + testSubject.processIntent(ViewIntent.ChangeApplicationGroup(item)) + + val itemsAfterAction = flow.first() + val expectedList = listOf( + createTextItem(R.string.split_tunneling_description), + createDivider(1), + createMainItem(R.string.all_applications), + createApplicationItem(app, false), + ) + + assertLists(expectedList, itemsAfterAction) + + verifyAll { + mockedSplitTunneling.enabled + mockedSplitTunneling.excludedAppList + mockedSplitTunneling.includeApp(app.packageName) + } + } + + @Test + fun test_add_app_to_excluded() = runBlocking { + val flow = testSubject.listItems.drop(1) + val app = AppData("test", 0, "testName") + every { mockedSplitTunneling.excludedAppList } returns emptyList() + every { mockedSplitTunneling.excludeApp(app.packageName) } just Runs + async { + testSubject.processIntent(ViewIntent.ViewIsReady) + appsProviderDeferred.complete(listOf(app)) + } + + val listBeforeAction = flow.first() + val expectedListBeforeAction = listOf( + createTextItem(R.string.split_tunneling_description), + createDivider(1), + createMainItem(R.string.all_applications), + createApplicationItem(app, false), + ) + + assertLists(expectedListBeforeAction, listBeforeAction) + + val item = listBeforeAction.first { it.identifier == app.packageName } + testSubject.processIntent(ViewIntent.ChangeApplicationGroup(item)) + + val itemsAfterAction = flow.first() + val expectedList = listOf( + createTextItem(R.string.split_tunneling_description), + createDivider(0), + createMainItem(R.string.exclude_applications), + createApplicationItem(app, true), + ) + + assertLists(expectedList, itemsAfterAction) + + verifyAll { + mockedSplitTunneling.enabled + mockedSplitTunneling.excludedAppList + mockedSplitTunneling.excludeApp(app.packageName) + } + } + + private fun createApplicationItem( + appData: AppData, + checked: Boolean + ): ListItemData = ListItemData.build(appData.packageName) { + type = ListItemData.APPLICATION + text = appData.name + iconRes = appData.iconRes + action = ListItemData.ItemAction(appData.packageName) + widget = WidgetState.ImageState( + if (checked) R.drawable.ic_icons_remove else R.drawable.ic_icons_add + ) + } + + private fun createDivider(id: Int): ListItemData = ListItemData.build("space_$id") { + type = ListItemData.DIVIDER + } + + private fun createMainItem(@StringRes text: Int): ListItemData = + ListItemData.build("header_$text") { + type = ListItemData.ACTION + textRes = text + } + + private fun createTextItem(@StringRes text: Int): ListItemData = + ListItemData.build("text_$text") { + type = ListItemData.PLAIN + textRes = text + action = ListItemData.ItemAction(text.toString()) + } + + private fun createProgressItem(): ListItemData = ListItemData.build(identifier = "progress") { + type = ListItemData.PROGRESS + } +} |
