1
0
Fork 0

For #1710: Create scaffolding for Robolectric tests (#2282)

master
Colin Lee 2019-05-07 16:36:37 -05:00 committed by GitHub
parent 447b134fe9
commit 96b68948b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 239 additions and 49 deletions

View File

@ -73,6 +73,7 @@ android {
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
unitTests.includeAndroidResources = true
}
flavorDimensions "abi"
@ -122,7 +123,7 @@ android.applicationVariants.all { variant ->
if (hasTest) {
apply plugin: 'kotlin-allopen'
allOpen {
annotation("org.mozilla.fenix.test.Mockable")
annotation("org.mozilla.fenix.test.OpenClass")
}
}
@ -373,9 +374,11 @@ dependencies {
exclude group: 'com.android.support', module: 'support-annotations'
}
testImplementation Deps.junit_jupiter_api
testImplementation Deps.junit_jupiter_params
testImplementation Deps.junit_jupiter_engine
testImplementation Deps.junit
testImplementation Deps.robolectric
debugImplementation Deps.fragment_testing
testImplementation Deps.megazord_forUnitTests
testImplementation Deps.places_forUnitTests
testImplementation Deps.mockito_core
androidTestImplementation Deps.mockito_android

View File

@ -28,16 +28,20 @@ import org.mozilla.fenix.utils.Settings
import java.io.File
@SuppressLint("Registered")
@Suppress("TooManyFunctions")
open class FenixApplication : Application() {
lateinit var fretboard: Fretboard
lateinit var experimentLoader: Deferred<Boolean>
var experimentLoaderComplete: Boolean = false
val components by lazy { Components(this) }
open val components by lazy { Components(this) }
override fun onCreate() {
super.onCreate()
setupApplication()
}
open fun setupApplication() {
// loadExperiments does things that run in parallel with the rest of setup.
// Call the function as early as possible so there's maximum overlap.
experimentLoader = loadExperiments()

View File

@ -19,6 +19,7 @@ import org.mozilla.fenix.components.metrics.AdjustMetricsService
import org.mozilla.fenix.components.metrics.GleanMetricsService
import org.mozilla.fenix.components.metrics.LeanplumMetricsService
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.test.Mockable
import org.mozilla.fenix.utils.Settings
import org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID
import org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION
@ -26,6 +27,7 @@ import org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION
/**
* Component group for all functionality related to analytics e.g. crash reporting and telemetry.
*/
@Mockable
class Analytics(
private val context: Context
) {

View File

@ -14,11 +14,13 @@ import mozilla.components.feature.sync.BackgroundSyncManager
import mozilla.components.feature.sync.GlobalSyncableStoreProvider
import mozilla.components.service.fxa.Config
import mozilla.components.service.fxa.FxaAccountManager
import org.mozilla.fenix.test.Mockable
/**
* Component group for background services. These are the components that need to be accessed from within a
* background worker.
*/
@Mockable
class BackgroundServices(
context: Context,
historyStorage: PlacesHistoryStorage,

View File

@ -5,10 +5,12 @@
package org.mozilla.fenix.components
import android.content.Context
import org.mozilla.fenix.test.Mockable
/**
* Provides access to all components.
*/
@Mockable
class Components(private val context: Context) {
val backgroundServices by lazy { BackgroundServices(context, core.historyStorage, core.bookmarksStorage) }
val services by lazy { Services(backgroundServices.accountManager, useCases.tabsUseCases) }

View File

@ -25,6 +25,7 @@ import mozilla.components.concept.fetch.Client
import mozilla.components.feature.session.HistoryDelegate
import mozilla.components.lib.crash.handler.CrashHandlerService
import org.mozilla.fenix.AppRequestInterceptor
import org.mozilla.fenix.test.Mockable
import org.mozilla.fenix.utils.Settings
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoRuntimeSettings
@ -33,9 +34,10 @@ import java.util.concurrent.TimeUnit
/**
* Component group for all core browser functionality.
*/
@Mockable
class Core(private val context: Context) {
private val runtime by lazy {
protected val runtime by lazy {
val builder = GeckoRuntimeSettings.Builder()
testConfig?.let {

View File

@ -5,18 +5,20 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.components
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.snackbar.BaseTransientBottomBar
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.widget.TextViewCompat
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fenix_snackbar.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.test.Mockable
@Mockable
class FenixSnackbar private constructor(
parent: ViewGroup,
content: View,

View File

@ -16,7 +16,9 @@ import mozilla.components.feature.findinpage.view.FindInPageBar
import mozilla.components.feature.findinpage.view.FindInPageView
import mozilla.components.support.base.feature.BackHandler
import mozilla.components.support.base.feature.LifecycleAwareFeature
import org.mozilla.fenix.test.Mockable
@Mockable
class FindInPageIntegration(
private val sessionManager: SessionManager,
private val view: FindInPageView,

View File

@ -8,11 +8,13 @@ import android.content.Context
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import mozilla.components.browser.search.SearchEngineManager
import org.mozilla.fenix.test.Mockable
import org.mozilla.fenix.utils.Settings
/**
* Component group for all search engine integration related functionality.
*/
@Mockable
class Search(private val context: Context) {
/**

View File

@ -7,10 +7,12 @@ package org.mozilla.fenix.components
import mozilla.components.feature.accounts.FirefoxAccountsAuthFeature
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.service.fxa.FxaAccountManager
import org.mozilla.fenix.test.Mockable
/**
* Component group which encapsulates foreground-friendly services.
*/
@Mockable
class Services(
private val accountManager: FxaAccountManager,
private val tabsUseCases: TabsUseCases

View File

@ -9,7 +9,9 @@ import androidx.paging.DataSource
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissions.Status
import mozilla.components.feature.sitepermissions.SitePermissionsStorage
import org.mozilla.fenix.test.Mockable
@Mockable
class Storage(private val context: Context) {
private val permissionsStorage by lazy {

View File

@ -10,11 +10,13 @@ import mozilla.components.browser.session.SessionManager
import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.tabs.TabsUseCases
import org.mozilla.fenix.test.Mockable
/**
* Component group for all use cases. Use cases are provided by feature
* modules and can be triggered by UI interactions.
*/
@Mockable
class UseCases(
private val context: Context,
private val sessionManager: SessionManager,

View File

@ -11,10 +11,12 @@ import mozilla.components.feature.intent.IntentProcessor
import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionUseCases
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.test.Mockable
/**
* Component group for miscellaneous components.
*/
@Mockable
class Utilities(
private val context: Context,
private val sessionManager: SessionManager,

View File

@ -58,8 +58,8 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
private lateinit var job: Job
private lateinit var bookmarkComponent: BookmarkComponent
private lateinit var signInComponent: SignInComponent
private var currentRoot: BookmarkNode? = null
private val navigation by lazy { Navigation.findNavController(requireActivity(), R.id.container) }
var currentRoot: BookmarkNode? = null
private val navigation by lazy { Navigation.findNavController(requireView()) }
private val onDestinationChangedListener =
NavController.OnDestinationChangedListener { _, destination, args ->
if (destination.id != R.id.bookmarkFragment ||
@ -67,6 +67,7 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
)
getManagedEmitter<BookmarkChange>().onNext(BookmarkChange.ClearSelection)
}
lateinit var initialJob: Job
override val coroutineContext: CoroutineContext
get() = Main + job
@ -81,19 +82,23 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
(activity as AppCompatActivity).title = getString(R.string.library_bookmarks)
activity?.title = getString(R.string.library_bookmarks)
setHasOptionsMenu(true)
}
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).supportActionBar?.show()
(activity as? AppCompatActivity)?.supportActionBar?.show()
checkIfSignedIn()
navigation.addOnDestinationChangedListener(onDestinationChangedListener)
val currentGuid = BookmarkFragmentArgs.fromBundle(arguments!!).currentRoot.ifEmpty { BookmarkRoot.Mobile.id }
launch(IO) {
initialJob = loadInitialBookmarkFolder(currentGuid)
}
private fun loadInitialBookmarkFolder(currentGuid: String): Job {
return launch(IO) {
currentRoot = requireComponents.core.bookmarksStorage.getTree(currentGuid) as BookmarkNode
launch(Main) {
@ -322,7 +327,8 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
override fun onProfileUpdated(profile: Profile) {
}
private fun getSelectedBookmarks() = (bookmarkComponent.uiView as BookmarkUIView).getSelected()
fun getBookmarks() = (bookmarkComponent.uiView as BookmarkUIView).tree?.children
fun getSelectedBookmarks() = (bookmarkComponent.uiView as BookmarkUIView).getSelected()
private suspend fun deleteSelectedBookmarks(
selected: Set<BookmarkNode> = getSelectedBookmarks(),

View File

@ -92,23 +92,24 @@ class BookmarkUIView(
fun getSelected(): Set<BookmarkNode> = bookmarkAdapter.selected
private fun setToolbarColors(foreground: Int, background: Int) {
val toolbar = (activity as AppCompatActivity).findViewById<Toolbar>(R.id.navigationToolbar)
val toolbar = activity?.findViewById<Toolbar>(R.id.navigationToolbar)
val colorFilter = PorterDuffColorFilter(
ContextCompat.getColor(context, foreground), PorterDuff.Mode.SRC_IN
)
toolbar.setBackgroundColor(ContextCompat.getColor(context, background))
toolbar.setTitleTextColor(ContextCompat.getColor(context, foreground))
themeToolbar(
toolbar, foreground,
background, colorFilter
)
toolbar?.run {
setBackgroundColor(ContextCompat.getColor(context, background))
setTitleTextColor(ContextCompat.getColor(context, foreground))
themeToolbar(
toolbar, foreground,
background, colorFilter
)
}
}
private fun setUIForSelectingMode(
mode: BookmarkState.Mode.Selecting
) {
(activity as? AppCompatActivity)?.title =
activity?.title =
context.getString(R.string.bookmarks_multi_select_title, mode.selectedItems.size)
setToolbarColors(
R.color.white_color,

View File

@ -0,0 +1,17 @@
/* 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
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.components.TestComponents
class TestApplication : FenixApplication() {
override val components: Components
get() = TestComponents(this)
override fun setupApplication() {
}
}

View File

@ -0,0 +1,18 @@
/* 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.components
import android.content.Context
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import mozilla.components.feature.sync.BackgroundSyncManager
class TestBackgroundServices(
context: Context,
historyStorage: PlacesHistoryStorage,
bookmarksStorage: PlacesBookmarksStorage
) : BackgroundServices(context, historyStorage, bookmarksStorage) {
override val syncManager = BackgroundSyncManager("")
}

View File

@ -0,0 +1,24 @@
package org.mozilla.fenix.components
import android.content.Context
import io.mockk.mockk
class TestComponents(private val context: Context) : Components(context) {
override val backgroundServices by lazy {
mockk<BackgroundServices>(relaxed = true)
}
override val services by lazy { Services(backgroundServices.accountManager, useCases.tabsUseCases) }
override val core by lazy { TestCore(context) }
override val search by lazy { Search(context) }
override val useCases by lazy { UseCases(context, core.sessionManager, search.searchEngineManager) }
override val utils by lazy {
Utilities(
context,
core.sessionManager,
useCases.sessionUseCases,
useCases.searchUseCases
)
}
override val analytics by lazy { Analytics(context) }
override val storage by lazy { Storage(context) }
}

View File

@ -0,0 +1,20 @@
/* 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.components
import android.content.Context
import io.mockk.mockk
import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.components.browser.engine.gecko.GeckoEngine
import mozilla.components.browser.session.SessionManager
import org.mozilla.geckoview.GeckoRuntime
@ObsoleteCoroutinesApi
class TestCore(private val context: Context) : Core(context) {
override val runtime = mockk<GeckoRuntime>(relaxed = true)
override val engine = mockk<GeckoEngine>(relaxed = true)
override val sessionManager = SessionManager(engine)
}

View File

@ -16,11 +16,11 @@ import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.spyk
import io.mockk.verify
import org.junit.jupiter.api.Test
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNull
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyString
import java.io.IOException

View File

@ -14,8 +14,8 @@ import io.reactivex.Observer
import io.reactivex.observers.TestObserver
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.TestUtils.setRxSchedulers
internal class BookmarkAdapterTest {
@ -23,7 +23,7 @@ internal class BookmarkAdapterTest {
private lateinit var bookmarkAdapter: BookmarkAdapter
private lateinit var emitter: Observer<BookmarkAction>
@BeforeEach
@Before
fun setup() {
setRxSchedulers()
emitter = TestObserver<BookmarkAction>()

View File

@ -13,8 +13,8 @@ import io.reactivex.observers.TestObserver
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.TestUtils
import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.UIView
@ -26,7 +26,7 @@ class BookmarkComponentTest {
private lateinit var bookmarkObserver: TestObserver<BookmarkState>
private lateinit var emitter: Observer<BookmarkChange>
@BeforeEach
@Before
fun setup() {
MockKAnnotations.init(this)
TestUtils.setRxSchedulers()

View File

@ -0,0 +1,60 @@
/* 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.library.bookmarks
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.navigation.NavController
import androidx.navigation.Navigation
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.appservices.places.BookmarkRoot
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.TestApplication
import org.mozilla.fenix.TestUtils
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@ObsoleteCoroutinesApi
@RunWith(RobolectricTestRunner::class)
@Config(application = TestApplication::class)
class BookmarkFragmentTest {
private lateinit var scenario: FragmentScenario<BookmarkFragment>
@Before
fun setup() {
TestUtils.setRxSchedulers()
val mockNavController = mockk<NavController>()
every { mockNavController.addOnDestinationChangedListener(any()) } just Runs
val args = BookmarkFragmentArgs(BookmarkRoot.Mobile.id).toBundle()
scenario =
launchFragmentInContainer<BookmarkFragment>(fragmentArgs = args, themeResId = R.style.NormalTheme) {
BookmarkFragment().also { fragment ->
fragment.viewLifecycleOwnerLiveData.observeForever {
if (it != null) {
Navigation.setViewNavController(fragment.requireView(), mockNavController)
}
}
}
}
}
@Test
fun `test initial bookmarks fragment ui`() {
scenario.onFragment { fragment ->
assertEquals(fragment.getString(R.string.library_bookmarks), fragment.activity?.title)
}
}
}

View File

@ -10,8 +10,8 @@ import io.mockk.mockk
import io.mockk.spyk
import io.reactivex.Observer
import io.reactivex.observers.TestObserver
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.TestUtils.bus
import org.mozilla.fenix.TestUtils.owner
import org.mozilla.fenix.TestUtils.setRxSchedulers
@ -25,7 +25,7 @@ class HistoryComponentTest {
private lateinit var historyObserver: TestObserver<HistoryState>
private lateinit var emitter: Observer<HistoryChange>
@BeforeEach
@Before
fun setup() {
MockKAnnotations.init(this)
setRxSchedulers()

View File

@ -1,7 +0,0 @@
/* 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.test
annotation class Mockable

View File

@ -0,0 +1,18 @@
/* 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.test
/**
* Annotate a class with [OpenClass] to open a class for mocking purposes while keeping it final in release builds
*/
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass
/**
* Annotate a class with [Mockable] to make it extensible in debug builds
*/
@OpenClass
@Target(AnnotationTarget.CLASS)
annotation class Mockable

View File

@ -23,15 +23,17 @@ private object Versions {
const val androidx_fragment = "1.1.0-alpha07"
const val androidx_navigation = "2.1.0-alpha02"
const val androidx_recyclerview = "1.1.0-alpha04"
const val androidx_testing = "1.1.0-alpha07"
const val appservices_gradle_plugin = "0.4.4"
const val mozilla_android_components = "0.52.0-SNAPSHOT"
const val mozilla_appservices = "0.27.0"
const val autodispose = "1.1.0"
const val adjust = "4.11.4"
const val installreferrer = "1.0"
const val junit_jupiter = "5.3.2"
const val junit = "4.12"
const val mockito = "2.23.0"
const val mockk = "1.9.kotlin12"
const val glide = "4.9.0"
@ -44,7 +46,7 @@ private object Versions {
const val tools_test_rules = "1.1.1"
const val tools_test_runner = "1.1.1"
const val uiautomator = "2.1.3"
const val test_tools = "1.0.2"
const val robolectric = "4.2"
const val google_ads_id_version = "16.0.0"
}
@ -146,10 +148,7 @@ object Deps {
const val adjust = "com.adjust.sdk:adjust-android:${Versions.adjust}"
const val installreferrer = "com.android.installreferrer:installreferrer:${Versions.installreferrer}"
const val junit_jupiter_api = "org.junit.jupiter:junit-jupiter-api:${Versions.junit_jupiter}"
const val junit_jupiter_params = "org.junit.jupiter:junit-jupiter-params:${Versions.junit_jupiter}"
const val junit_jupiter_engine = "org.junit.jupiter:junit-jupiter-engine:${Versions.junit_jupiter}"
const val junit = "junit:junit:${Versions.junit}"
const val mockito_core = "org.mockito:mockito-core:${Versions.mockito}"
const val mockito_android = "org.mockito:mockito-android:${Versions.mockito}"
const val mockk = "io.mockk:mockk:${Versions.mockk}"
@ -167,6 +166,10 @@ object Deps {
const val tools_test_rules = "com.android.support.test:rules:${Versions.tools_test_rules}"
const val tools_test_runner = "com.android.support.test:runner:${Versions.tools_test_runner}"
const val uiautomator = "com.android.support.test.uiautomator:uiautomator-v18:${Versions.uiautomator}"
const val robolectric = "org.robolectric:robolectric:${Versions.robolectric}"
const val fragment_testing = "androidx.fragment:fragment-testing:${Versions.androidx_testing}"
const val megazord_forUnitTests = "org.mozilla.appservices:fenix-megazord-forUnitTests:${Versions.mozilla_appservices}"
const val places_forUnitTests = "org.mozilla.appservices:places-forUnitTests:${Versions.mozilla_appservices}"
const val google_ads_id = "com.google.android.gms:play-services-ads-identifier:${Versions.google_ads_id_version}"
}

View File

@ -16,4 +16,5 @@ kotlin.code.style=official
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
android.enableR8.fullMode=true
android.enableR8.fullMode=true
android.enableUnitTestBinaryResources=true