Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
Fixes #589: Add sample unit tests for a component
Browse files Browse the repository at this point in the history
  • Loading branch information
colintheshots authored and boek committed Feb 20, 2019
1 parent a2031b9 commit 5cf61c9
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 26 deletions.
25 changes: 21 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,6 +26,7 @@ data class HistoryItem(val id: Int, val url: String) {
}
}

@Mockable
class HistoryComponent(
private val container: ViewGroup,
bus: ActionBusFactory,
Expand Down
17 changes: 0 additions & 17 deletions app/src/test/java/org/mozilla/fenix/ExampleUnitTest.kt

This file was deleted.

28 changes: 28 additions & 0 deletions app/src/test/java/org/mozilla/fenix/TestUtils.kt
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
7 changes: 7 additions & 0 deletions architecture/src/main/java/org/mozilla/fenix/test/Mockable.kt
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 15 additions & 2 deletions buildSrc/src/main/java/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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")
Expand All @@ -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}"

Expand Down Expand Up @@ -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}"

Expand All @@ -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}"
}

0 comments on commit 5cf61c9

Please sign in to comment.