summaryrefslogtreecommitdiffhomepage
path: root/android/src/androidTest
diff options
context:
space:
mode:
authorAleksandr Granin <aleksandr@mullvad.net>2021-04-06 14:43:30 +0200
committerAleksandr Granin <aleksandr@mullvad.net>2021-04-19 14:36:22 +0200
commitb3b5da44ec8bf1f2a39cb33ad31d89dd571ac731 (patch)
treef47b084bd46361fb26b95fd66091c393ed8151f7 /android/src/androidTest
parent585303d6cb04657d7466a4ba5e1bc6c6eb0227d0 (diff)
downloadmullvadvpn-b3b5da44ec8bf1f2a39cb33ad31d89dd571ac731.tar.xz
mullvadvpn-b3b5da44ec8bf1f2a39cb33ad31d89dd571ac731.zip
Refactor SplitTunneling Fragment, add tests.
Diffstat (limited to 'android/src/androidTest')
-rw-r--r--android/src/androidTest/kotlin/net/mullvad/mullvadvpn/RecyclerViewMatcher.kt52
-rw-r--r--android/src/androidTest/kotlin/net/mullvad/mullvadvpn/ui/fragments/SplitTunnelingFragmentTest.kt145
2 files changed, 197 insertions, 0 deletions
diff --git a/android/src/androidTest/kotlin/net/mullvad/mullvadvpn/RecyclerViewMatcher.kt b/android/src/androidTest/kotlin/net/mullvad/mullvadvpn/RecyclerViewMatcher.kt
new file mode 100644
index 0000000000..2e87df9720
--- /dev/null
+++ b/android/src/androidTest/kotlin/net/mullvad/mullvadvpn/RecyclerViewMatcher.kt
@@ -0,0 +1,52 @@
+package net.mullvad.mullvadvpn
+
+import android.content.res.Resources
+import android.content.res.Resources.NotFoundException
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import org.hamcrest.Description
+import org.hamcrest.Matcher
+import org.hamcrest.TypeSafeMatcher
+
+class RecyclerViewMatcher(private val recyclerViewId: Int) {
+ fun atPosition(position: Int): Matcher<View> {
+ return atPositionOnView(position)
+ }
+
+ fun atPositionOnView(position: Int, targetViewId: Int? = null): Matcher<View> =
+ object : TypeSafeMatcher<View>() {
+ var resources: Resources? = null
+ var childView: View? = null
+
+ override fun describeTo(description: Description) {
+ val idDescription = resources?.let {
+ try {
+ it.getResourceName(recyclerViewId)
+ } catch (var4: NotFoundException) {
+ "$recyclerViewId (resource name not found)"
+ }
+ } ?: recyclerViewId.toString()
+ description.appendText("with id: $idDescription")
+ }
+
+ override fun matchesSafely(view: View): Boolean {
+ resources = view.resources
+ val recyclerView =
+ view.rootView.findViewById<View>(recyclerViewId) as RecyclerView?
+ if (recyclerView == null || recyclerView.id != recyclerViewId) {
+ return false
+ }
+ childView = recyclerView.findViewHolderForAdapterPosition(position)?.itemView
+ val targetView = targetViewId?.let { id ->
+ childView?.findViewById<View>(id)
+ } ?: childView
+ return view == targetView
+ }
+ }
+
+ companion object {
+ fun withRecyclerView(recyclerViewId: Int): RecyclerViewMatcher {
+ return RecyclerViewMatcher(recyclerViewId)
+ }
+ }
+}
diff --git a/android/src/androidTest/kotlin/net/mullvad/mullvadvpn/ui/fragments/SplitTunnelingFragmentTest.kt b/android/src/androidTest/kotlin/net/mullvad/mullvadvpn/ui/fragments/SplitTunnelingFragmentTest.kt
new file mode 100644
index 0000000000..8bd3cc70b8
--- /dev/null
+++ b/android/src/androidTest/kotlin/net/mullvad/mullvadvpn/ui/fragments/SplitTunnelingFragmentTest.kt
@@ -0,0 +1,145 @@
+package net.mullvad.mullvadvpn.ui.fragments
+
+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.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+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.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.qualifier.named
+import org.koin.core.scope.Scope
+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 val scope: Scope = getKoin().getOrCreateScope(APPS_SCOPE, named(APPS_SCOPE))
+
+ @get:Rule
+ val mockProvider = MockProviderRule.create { clazz ->
+ when (clazz) {
+ SplitTunnelingViewModel::class -> mockedViewModel
+ else -> mockkClass(clazz)
+ }
+ }
+
+ @Before
+ fun setUp() {
+ scope.declareMock<SplitTunnelingViewModel>()
+ every { mockedViewModel.listItems } returns sharedFlow
+ coEvery { mockedViewModel.processIntent(ViewIntent.ViewIsReady) } just Runs
+ }
+
+ @After
+ fun tearDown() {
+ scope.close()
+ 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).atPosition(0)).perform(click())
+
+ coVerifyAll {
+ mockedViewModel.listItems
+ mockedViewModel.processIntent(ViewIntent.ChangeApplicationGroup(testListItem))
+ }
+ }
+}