Fixes #589: Add sample unit tests for a component
parent
a2031b92cd
commit
5cf61c95db
|
@ -65,6 +65,16 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
android.applicationVariants.all { variant ->
|
||||
boolean hasTest = gradle.startParameter.taskNames.find {it.contains("test") || it.contains("Test")} != null
|
||||
if (hasTest) {
|
||||
apply plugin: 'kotlin-allopen'
|
||||
allOpen {
|
||||
annotation("org.mozilla.fenix.test.Mockable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android.applicationVariants.all { variant ->
|
||||
def buildType = variant.buildType.name
|
||||
|
||||
|
@ -152,10 +162,6 @@ dependencies {
|
|||
debugImplementation Deps.leakcanary
|
||||
releaseImplementation Deps.leakcanary_noop
|
||||
|
||||
testImplementation Deps.junit
|
||||
androidTestImplementation Deps.tools_test_runner
|
||||
androidTestImplementation Deps.tools_espresso_core
|
||||
|
||||
armImplementation Deps.geckoview_nightly_arm
|
||||
x86Implementation Deps.geckoview_nightly_x86
|
||||
aarch64Implementation Deps.geckoview_nightly_aarch64
|
||||
|
@ -167,6 +173,17 @@ dependencies {
|
|||
implementation Deps.android_arch_navigation_ui
|
||||
|
||||
implementation Deps.autodispose
|
||||
|
||||
androidTestImplementation Deps.tools_test_runner
|
||||
androidTestImplementation Deps.tools_espresso_core
|
||||
|
||||
testImplementation Deps.junit_jupiter_api
|
||||
testImplementation Deps.junit_jupiter_params
|
||||
testImplementation Deps.junit_jupiter_engine
|
||||
|
||||
testImplementation Deps.mockito_core
|
||||
androidTestImplementation Deps.mockito_android
|
||||
testImplementation Deps.mockk
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package org.mozilla.fenix.library.history
|
||||
|
||||
import android.view.ViewGroup
|
||||
import org.mozilla.fenix.test.Mockable
|
||||
import org.mozilla.fenix.mvi.Action
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.Change
|
||||
|
@ -25,6 +26,7 @@ data class HistoryItem(val id: Int, val url: String) {
|
|||
}
|
||||
}
|
||||
|
||||
@Mockable
|
||||
class HistoryComponent(
|
||||
private val container: ViewGroup,
|
||||
bus: ActionBusFactory,
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
package org.mozilla.fenix
|
||||
|
||||
import org.junit.Test
|
||||
/* ktlint-disable no-wildcard-imports */
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/* 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 androidx.lifecycle.LifecycleOwner
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.reactivex.android.plugins.RxAndroidPlugins
|
||||
import io.reactivex.plugins.RxJavaPlugins
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
|
||||
object TestUtils {
|
||||
fun setRxSchedulers() {
|
||||
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
|
||||
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
|
||||
}
|
||||
|
||||
val owner = mockk<LifecycleOwner> {
|
||||
every { lifecycle } returns mockk()
|
||||
every { lifecycle.addObserver(any()) } just Runs
|
||||
}
|
||||
val bus: ActionBusFactory = ActionBusFactory.get(owner)
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/* 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.history
|
||||
|
||||
import android.view.ViewGroup
|
||||
import io.mockk.MockKAnnotations
|
||||
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.mozilla.fenix.TestUtils.bus
|
||||
import org.mozilla.fenix.TestUtils.owner
|
||||
import org.mozilla.fenix.TestUtils.setRxSchedulers
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.UIView
|
||||
import org.mozilla.fenix.mvi.getManagedEmitter
|
||||
|
||||
class HistoryComponentTest {
|
||||
|
||||
private lateinit var historyComponent: TestHistoryComponent
|
||||
private lateinit var historyObserver: TestObserver<HistoryState>
|
||||
private lateinit var emitter: Observer<HistoryChange>
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
setRxSchedulers()
|
||||
|
||||
historyComponent = spyk(
|
||||
TestHistoryComponent(mockk(), bus),
|
||||
recordPrivateCalls = true
|
||||
)
|
||||
historyObserver = historyComponent.internalRender(historyComponent.reducer).test()
|
||||
emitter = owner.getManagedEmitter()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `add and remove one history item normally`() {
|
||||
val historyItem = HistoryItem(123, "http://mozilla.org")
|
||||
|
||||
emitter.onNext(HistoryChange.Change(listOf(historyItem)))
|
||||
emitter.onNext(HistoryChange.EnterEditMode(historyItem))
|
||||
emitter.onNext(HistoryChange.RemoveItemForRemoval(historyItem))
|
||||
emitter.onNext(HistoryChange.AddItemForRemoval(historyItem))
|
||||
emitter.onNext(HistoryChange.ExitEditMode)
|
||||
|
||||
historyObserver.assertSubscribed().awaitCount(6).assertNoErrors()
|
||||
.assertValues(
|
||||
HistoryState(listOf(), HistoryState.Mode.Normal),
|
||||
HistoryState(listOf(historyItem), HistoryState.Mode.Normal),
|
||||
HistoryState(listOf(historyItem), HistoryState.Mode.Editing(listOf(historyItem))),
|
||||
HistoryState(listOf(historyItem), HistoryState.Mode.Editing(listOf())),
|
||||
HistoryState(listOf(historyItem), HistoryState.Mode.Editing(listOf(historyItem))),
|
||||
HistoryState(listOf(historyItem), HistoryState.Mode.Normal)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `try making changes when not in edit mode`() {
|
||||
val historyItems = listOf(
|
||||
HistoryItem(1337, "http://reddit.com"),
|
||||
HistoryItem(31337, "http://leethaxor.com")
|
||||
)
|
||||
|
||||
emitter.onNext(HistoryChange.Change(historyItems))
|
||||
emitter.onNext(HistoryChange.AddItemForRemoval(historyItems[0]))
|
||||
emitter.onNext(HistoryChange.EnterEditMode(historyItems[0]))
|
||||
emitter.onNext(HistoryChange.ExitEditMode)
|
||||
|
||||
historyObserver.assertSubscribed().awaitCount(4).assertNoErrors()
|
||||
.assertValues(
|
||||
HistoryState(listOf(), HistoryState.Mode.Normal),
|
||||
HistoryState(historyItems, HistoryState.Mode.Normal),
|
||||
HistoryState(historyItems, HistoryState.Mode.Editing(listOf(historyItems[0]))),
|
||||
HistoryState(historyItems, HistoryState.Mode.Normal)
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class TestHistoryComponent(container: ViewGroup, bus: ActionBusFactory) :
|
||||
HistoryComponent(container, bus) {
|
||||
|
||||
override val uiView: UIView<HistoryState, HistoryAction, HistoryChange>
|
||||
get() = mockk(relaxed = true)
|
||||
}
|
||||
}
|
|
@ -17,19 +17,22 @@ abstract class UIComponent<S : ViewState, A : Action, C : Change>(
|
|||
|
||||
abstract var initialState: S
|
||||
abstract val reducer: Reducer<S, C>
|
||||
val uiView: UIView<S, A, C> by lazy { initView() }
|
||||
|
||||
open val uiView: UIView<S, A, C> by lazy { initView() }
|
||||
|
||||
abstract fun initView(): UIView<S, A, C>
|
||||
open fun getContainerId() = uiView.containerId
|
||||
|
||||
/**
|
||||
* Render the ViewState to the View through the Reducer
|
||||
*/
|
||||
fun render(reducer: Reducer<S, C>): Disposable =
|
||||
internalRender(reducer)
|
||||
.subscribe(uiView.updateView())
|
||||
|
||||
fun internalRender(reducer: Reducer<S, C>): Observable<S> =
|
||||
changesObservable
|
||||
.scan(initialState, reducer)
|
||||
.distinctUntilChanged()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(uiView.updateView())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
/* 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
|
|
@ -9,6 +9,7 @@ buildscript {
|
|||
classpath Deps.tools_androidgradle
|
||||
classpath Deps.tools_kotlingradle
|
||||
classpath Deps.androidx_safeargs
|
||||
classpath Deps.allopen
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
private object Versions {
|
||||
const val kotlin = "1.3.11"
|
||||
const val android_gradle_plugin = "3.2.1"
|
||||
|
||||
const val geckoNightly = "67.0.20190213102848"
|
||||
const val rxAndroid = "2.1.0"
|
||||
const val rxKotlin = "2.3.0"
|
||||
|
@ -23,13 +24,16 @@ private object Versions {
|
|||
|
||||
const val mozilla_android_components = "0.43.0-SNAPSHOT"
|
||||
|
||||
const val junit = "4.12"
|
||||
const val test_tools = "1.0.2"
|
||||
const val espresso_core = "2.2.2"
|
||||
|
||||
const val android_arch_navigation = "1.0.0-beta02"
|
||||
|
||||
const val autodispose = "1.1.0"
|
||||
|
||||
const val junit_jupiter = "5.3.2"
|
||||
const val mockito = "2.23.0"
|
||||
const val mockk = "1.9.kotlin12"
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
|
@ -38,6 +42,8 @@ object Deps {
|
|||
const val tools_kotlingradle = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
|
||||
const val kotlin_stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}"
|
||||
|
||||
const val allopen = "org.jetbrains.kotlin:kotlin-allopen:${Versions.kotlin}"
|
||||
|
||||
const val rxKotlin = "io.reactivex.rxjava2:rxkotlin:${Versions.rxKotlin}"
|
||||
const val rxAndroid = "io.reactivex.rxjava2:rxandroid:${Versions.rxAndroid}"
|
||||
|
||||
|
@ -96,7 +102,6 @@ object Deps {
|
|||
const val leakcanary = "com.squareup.leakcanary:leakcanary-android:${Versions.leakcanary}"
|
||||
const val leakcanary_noop = "com.squareup.leakcanary:leakcanary-android-no-op:${Versions.leakcanary}"
|
||||
|
||||
const val junit = "junit:junit:${Versions.junit}"
|
||||
const val tools_test_runner = "com.android.support.test:runner:${Versions.test_tools}"
|
||||
const val tools_espresso_core = "com.android.support.test.espresso:espresso-core:${Versions.espresso_core}"
|
||||
|
||||
|
@ -115,5 +120,13 @@ object Deps {
|
|||
const val autodispose_android = "com.uber.autodispose:autodispose-android:${Versions.autodispose}"
|
||||
const val autodispose_android_aac = "com.uber.autodispose:autodispose-android-archcomponents:${Versions.autodispose}"
|
||||
const val autodispose_android_aac_test = "com.uber.autodispose:autodispose-android-archcomponents-test:${Versions.autodispose}"
|
||||
|
||||
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 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}"
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue