1
0
Fork 0

For 4007 - Add unit tests for the Share Interactor, Controller and list Adapters

master
Mugurell 2019-08-29 18:47:49 +03:00 committed by Jeff Boek
parent e165868a53
commit bf5b0e5cda
7 changed files with 474 additions and 6 deletions

View File

@ -8,6 +8,7 @@ import android.content.Intent
import android.content.Intent.ACTION_SEND
import android.content.Intent.EXTRA_TEXT
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.navigation.NavController
@ -53,10 +54,8 @@ class DefaultShareController(
}
override fun handleShareToApp(app: AppShareOption) {
val shareText = tabs.joinToString("\n") { tab -> tab.url }
val intent = Intent(ACTION_SEND).apply {
putExtra(EXTRA_TEXT, shareText)
putExtra(EXTRA_TEXT, getShareText())
type = "text/plain"
flags = FLAG_ACTIVITY_NEW_TASK
setClassName(app.packageName, app.activityName)
@ -91,7 +90,8 @@ class DefaultShareController(
dismiss()
}
private fun sendTab(deviceId: String) {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun sendTab(deviceId: String) {
account?.run {
tabs.forEach { tab ->
deviceConstellation().sendEventToDeviceAsync(
@ -101,4 +101,7 @@ class DefaultShareController(
}
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun getShareText() = tabs.joinToString("\n") { tab -> tab.url }
}

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.share.viewholders
import android.content.Context
import android.graphics.PorterDuff
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.account_share_list_item.view.*
@ -17,7 +18,8 @@ import org.mozilla.fenix.share.listadapters.SyncShareOption
class AccountDeviceViewHolder(
itemView: View,
private val interactor: ShareToAccountDevicesInteractor
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
val interactor: ShareToAccountDevicesInteractor
) : RecyclerView.ViewHolder(itemView) {
private val context: Context = itemView.context

View File

@ -5,6 +5,7 @@
package org.mozilla.fenix.share.viewholders
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.app_share_list_item.view.*
import org.mozilla.fenix.R
@ -13,7 +14,8 @@ import org.mozilla.fenix.share.listadapters.AppShareOption
class AppViewHolder(
itemView: View,
interactor: ShareToAppsInteractor
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
val interactor: ShareToAppsInteractor
) : RecyclerView.ViewHolder(itemView) {
private var application: AppShareOption? = null

View File

@ -0,0 +1,190 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.share
import android.content.Intent
import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import assertk.assertAll
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isTrue
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.slot
import io.mockk.spyk
import io.mockk.verify
import io.mockk.verifyOrder
import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.components.concept.sync.Device
import mozilla.components.concept.sync.DeviceEventOutgoing
import mozilla.components.concept.sync.DeviceType
import mozilla.components.concept.sync.OAuthAccount
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.TestApplication
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.share.listadapters.AppShareOption
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@UseExperimental(ObsoleteCoroutinesApi::class)
@RunWith(RobolectricTestRunner::class)
@Config(application = TestApplication::class)
class ShareControllerTest {
private val fragment = mockk<Fragment>(relaxed = true)
private val tabsToShare = listOf(
ShareTab("url0", "title0", "sessionId0"),
ShareTab("url1", "title1")
)
private val textToShare = "${tabsToShare[0].url}\n${tabsToShare[1].url}"
private val account = mockk<OAuthAccount>(relaxed = true)
private val navController = mockk<NavController>(relaxed = true)
private val dismiss = mockk<() -> Unit>(relaxed = true)
// Use a spy that allows overriding "controller.sendTab below"
private val controller = spyk(DefaultShareController(fragment, tabsToShare, account, navController, dismiss))
@Test
fun `handleShareClosed should call a passed in delegate to close this`() {
controller.handleShareClosed()
verify { dismiss() }
}
@Test
fun `handleShareToApp should start a new sharing activity and close this`() {
val appPackageName = "package"
val appClassName = "activity"
val appShareOption = AppShareOption("app", mockk(), appPackageName, appClassName)
val shareIntent = slot<Intent>()
every { fragment.startActivity(capture(shareIntent)) } just Runs
controller.handleShareToApp(appShareOption)
// Check that the Intent used for querying apps has the expected structre
assertAll {
assertThat(shareIntent.isCaptured).isTrue()
assertThat(shareIntent.captured.action).isEqualTo(Intent.ACTION_SEND)
assertThat(shareIntent.captured.extras!![Intent.EXTRA_TEXT]).isEqualTo(textToShare)
assertThat(shareIntent.captured.type).isEqualTo("text/plain")
assertThat(shareIntent.captured.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
assertThat(shareIntent.captured.component!!.packageName).isEqualTo(appPackageName)
assertThat(shareIntent.captured.component!!.className).isEqualTo(appClassName)
}
verifyOrder {
fragment.startActivity(shareIntent.captured)
dismiss()
}
}
@Test
@Suppress("DeferredResultUnused")
fun `handleShareToDevice should share to account device, inform callbacks and dismiss`() {
val deviceToShareTo =
Device("deviceId", "deviceName", DeviceType.UNKNOWN, false, 0L, emptyList(), false, null)
val tabSharedCallbackActivity = mockk<HomeActivity>(relaxed = true)
val sharedTabsNumber = slot<Int>()
val deviceId = slot<String>()
every { fragment.activity } returns tabSharedCallbackActivity
controller.handleShareToDevice(deviceToShareTo)
// Verify all the needed methods are called.
verify { controller.sendTab(capture(deviceId)) }
verify { tabSharedCallbackActivity.onTabsShared(capture(sharedTabsNumber)) }
verify { dismiss() }
assertAll {
// sendTab() should be called for each device in the account
assertThat(deviceId.isCaptured).isTrue()
assertThat(deviceId.captured).isEqualTo(deviceToShareTo.id)
// All current tabs should be shared
assertThat(sharedTabsNumber.isCaptured).isTrue()
assertThat(sharedTabsNumber.captured).isEqualTo(tabsToShare.size)
}
}
@Test
fun `handleShareToAllDevices calls handleShareToDevice multiple times`() {
val devicesToShareTo = listOf(
Device("deviceId0", "deviceName0", DeviceType.UNKNOWN, false, 0L, emptyList(), false, null),
Device("deviceId1", "deviceName1", DeviceType.UNKNOWN, true, 1L, emptyList(), false, null)
)
val tabSharedCallbackActivity = mockk<HomeActivity>(relaxed = true)
val sharedTabsNumber = slot<Int>()
val sharedToDeviceIds = mutableListOf<String>()
every { fragment.activity } returns tabSharedCallbackActivity
controller.handleShareToAllDevices(devicesToShareTo)
// Verify all the needed methods are called. sendTab() should be called for each account device.
verify(exactly = devicesToShareTo.size) { controller.sendTab(capture(sharedToDeviceIds)) }
verify { tabSharedCallbackActivity.onTabsShared(capture(sharedTabsNumber)) }
verify { dismiss() }
assertAll {
// sendTab() should be called for each device in the account
assertThat(sharedToDeviceIds.size).isEqualTo(devicesToShareTo.size)
sharedToDeviceIds.forEachIndexed { index, shareToDeviceId ->
assertThat(shareToDeviceId).isEqualTo(devicesToShareTo[index].id)
}
// All current tabs should be shared
assertThat(sharedTabsNumber.isCaptured).isTrue()
assertThat(sharedTabsNumber.captured).isEqualTo(tabsToShare.size)
}
}
@Test
fun `handleSignIn should navigate to the Sync Fragment and dismiss this one`() {
controller.handleSignIn()
verifyOrder {
navController.nav(
R.id.shareFragment,
ShareFragmentDirections.actionShareFragmentToTurnOnSyncFragment()
)
dismiss()
}
}
@Test
@Suppress("DeferredResultUnused")
fun `sendTab should send all current tabs to the selected device`() {
val deviceToShareTo =
Device("deviceId", "deviceName", DeviceType.UNKNOWN, false, 0L, emptyList(), false, null)
val sharedToDeviceIds = mutableListOf<String>()
val outgoingEvents = mutableListOf<DeviceEventOutgoing.SendTab>()
controller.sendTab(deviceToShareTo.id)
// Verify the sync components being called and record the sent values
verify {
account.deviceConstellation()
.sendEventToDeviceAsync(capture(sharedToDeviceIds), capture(outgoingEvents))
}
assertAll {
// All Tabs should be sent to the same device
assertThat(sharedToDeviceIds.size).isEqualTo(tabsToShare.size)
sharedToDeviceIds.forEach { sharedToDeviceId ->
assertThat(sharedToDeviceId).isEqualTo(deviceToShareTo.id)
}
// There should be an DeviceEventOutgoing.SendTab for each sent Tab
assertThat(outgoingEvents.size).isEqualTo(outgoingEvents.size)
outgoingEvents.forEachIndexed { index, event ->
assertThat((event).title).isEqualTo(tabsToShare[index].title)
assertThat((event).url).isEqualTo(tabsToShare[index].url)
}
}
}
@Test
fun `getShareText should respect concatenate shared tabs urls`() {
assertThat(controller.getShareText()).isEqualTo(textToShare)
}
}

View File

@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.share
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.concept.sync.Device
import org.junit.Test
import org.mozilla.fenix.share.listadapters.AppShareOption
class ShareInteractorTest {
private val controller = mockk<ShareController>(relaxed = true)
private val interactor = ShareInteractor(controller)
@Test
fun onShareClosed() {
interactor.onShareClosed()
verify { controller.handleShareClosed() }
}
@Test
fun onSignIn() {
interactor.onSignIn()
verify { controller.handleSignIn() }
}
@Test
fun onAddNewDevice() {
interactor.onAddNewDevice()
verify { controller.handleAddNewDevice() }
}
@Test
fun onShareToDevice() {
val device = mockk<Device>()
interactor.onShareToDevice(device)
verify { controller.handleShareToDevice(device) }
}
@Test
fun onSendToAllDevices() {
val devices = emptyList<Device>()
interactor.onShareToAllDevices(devices)
verify { controller.handleShareToAllDevices(devices) }
}
@Test
fun onShareToApp() {
val app = mockk<AppShareOption>()
interactor.onShareToApp(app)
verify { controller.handleShareToApp(app) }
}
}

View File

@ -0,0 +1,102 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.share.listadapters
import android.view.ViewGroup
import assertk.assertThat
import assertk.assertions.isEqualTo
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import io.mockk.verifyOrder
import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.components.support.test.robolectric.testContext
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.TestApplication
import org.mozilla.fenix.share.ShareInteractor
import org.mozilla.fenix.share.viewholders.AccountDeviceViewHolder
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@UseExperimental(ObsoleteCoroutinesApi::class)
@RunWith(RobolectricTestRunner::class)
@Config(application = TestApplication::class)
class AccountDevicesShareAdapterTest {
private val syncOptions = mutableListOf(SyncShareOption.AddNewDevice, SyncShareOption.SignIn)
private val syncOptionsEmpty = mutableListOf<SyncShareOption>()
private val interactor: ShareInteractor = mockk(relaxed = true)
@Test
fun `updateData should replace all previous data with argument and call notifyDataSetChanged()`() {
// Used AccountDevicesShareAdapter as a spy to ease testing of notifyDataSetChanged()
// and syncOptionsEmpty to be able to record them being called
val adapter = spyk(AccountDevicesShareAdapter(mockk(), syncOptionsEmpty))
every { adapter.notifyDataSetChanged() } just Runs
adapter.updateData(syncOptions)
verifyOrder {
syncOptionsEmpty.clear()
syncOptionsEmpty.addAll(syncOptions)
adapter.notifyDataSetChanged()
}
}
@Test
fun `getItemCount on a default instantiated Adapter should return 0`() {
val adapter = AccountDevicesShareAdapter(mockk())
assertThat(adapter.itemCount).isEqualTo(0)
}
@Test
fun `getItemCount after updateData() call should return the the passed in list's size`() {
val adapter = AccountDevicesShareAdapter(mockk(), syncOptions)
assertThat(adapter.itemCount).isEqualTo(2)
}
@Test
fun `the adapter uses the right ViewHolder`() {
val adapter = AccountDevicesShareAdapter(interactor)
val parentView: ViewGroup = mockk(relaxed = true)
every { parentView.context } returns testContext
val viewHolder = adapter.onCreateViewHolder(parentView, 0)
assertThat(viewHolder::class).isEqualTo(AccountDeviceViewHolder::class)
}
@Test
fun `the adapter passes the Interactor to the ViewHolder`() {
val adapter = AccountDevicesShareAdapter(interactor)
val parentView: ViewGroup = mockk(relaxed = true)
every { parentView.context } returns testContext
val viewHolder = adapter.onCreateViewHolder(parentView, 0)
assertThat(viewHolder.interactor).isEqualTo(interactor)
}
@Test
fun `the adapter binds the right item to a ViewHolder`() {
val adapter = AccountDevicesShareAdapter(interactor, syncOptions)
val parentView: ViewGroup = mockk(relaxed = true)
val itemView: ViewGroup = mockk(relaxed = true)
every { parentView.context } returns testContext
every { itemView.context } returns testContext
val viewHolder = spyk(AccountDeviceViewHolder(parentView, mockk()))
every { adapter.onCreateViewHolder(parentView, 0) } returns viewHolder
every { viewHolder.bind(any()) } just Runs
adapter.bindViewHolder(viewHolder, 1)
verify { viewHolder.bind(syncOptions[1]) }
}
}

View File

@ -0,0 +1,105 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.share.listadapters
import android.view.ViewGroup
import assertk.assertThat
import assertk.assertions.isEqualTo
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import io.mockk.verifyOrder
import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.components.support.test.robolectric.testContext
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.TestApplication
import org.mozilla.fenix.share.ShareInteractor
import org.mozilla.fenix.share.viewholders.AppViewHolder
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@UseExperimental(ObsoleteCoroutinesApi::class)
@RunWith(RobolectricTestRunner::class)
@Config(application = TestApplication::class)
class AppShareAdapterTest {
private val appOptions = mutableListOf(
AppShareOption("App 0", mockk(), "package 0", "activity 0"),
AppShareOption("App 1", mockk(), "package 1", "activity 1")
)
private val appOptionsEmpty = mutableListOf<AppShareOption>()
private val interactor: ShareInteractor = mockk(relaxed = true)
@Test
fun `updateData should replace all previous data with argument and call notifyDataSetChanged()`() {
// Used AppShareAdapter as a spy to ease testing of notifyDataSetChanged()
// and appOptionsEmpty to be able to record them being called
val adapter = spyk(AppShareAdapter(mockk(), appOptionsEmpty))
every { adapter.notifyDataSetChanged() } just Runs
adapter.updateData(appOptions)
verifyOrder {
appOptionsEmpty.clear()
appOptionsEmpty.addAll(appOptions)
adapter.notifyDataSetChanged()
}
}
@Test
fun `getItemCount on a default instantiated Adapter should return 0`() {
val adapter = AppShareAdapter(mockk())
assertThat(adapter.itemCount).isEqualTo(0)
}
@Test
fun `getItemCount after updateData() call should return the the passed in list's size`() {
val adapter = AppShareAdapter(mockk(), appOptions)
assertThat(adapter.itemCount).isEqualTo(2)
}
@Test
fun `the adapter uses the right ViewHolder`() {
val adapter = AppShareAdapter(interactor)
val parentView: ViewGroup = mockk(relaxed = true)
every { parentView.context } returns testContext
val viewHolder = adapter.onCreateViewHolder(parentView, 0)
assertThat(viewHolder::class).isEqualTo(AppViewHolder::class)
}
@Test
fun `the adapter passes the Interactor to the ViewHolder`() {
val adapter = AppShareAdapter(interactor)
val parentView: ViewGroup = mockk(relaxed = true)
every { parentView.context } returns testContext
val viewHolder = adapter.onCreateViewHolder(parentView, 0)
assertThat(viewHolder.interactor).isEqualTo(interactor)
}
@Test
fun `the adapter binds the right item to a ViewHolder`() {
val adapter = AppShareAdapter(interactor, appOptions)
val parentView: ViewGroup = mockk(relaxed = true)
val itemView: ViewGroup = mockk(relaxed = true)
every { parentView.context } returns testContext
every { itemView.context } returns testContext
val viewHolder = spyk(AppViewHolder(parentView, mockk()))
every { adapter.onCreateViewHolder(parentView, 0) } returns viewHolder
every { viewHolder.bind(any()) } just Runs
adapter.bindViewHolder(viewHolder, 1)
verify { viewHolder.bind(appOptions[1]) }
}
}