diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /mobile/android/android-components/components/support/test | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mobile/android/android-components/components/support/test')
27 files changed, 739 insertions, 0 deletions
diff --git a/mobile/android/android-components/components/support/test/README.md b/mobile/android/android-components/components/support/test/README.md new file mode 100644 index 0000000000..38c8f1ec54 --- /dev/null +++ b/mobile/android/android-components/components/support/test/README.md @@ -0,0 +1,19 @@ +# [Android Components](../../../README.md) > Support > Test + +A collection of helpers for testing components in local unit tests (`src/test`). + +## Usage + +### Setting up the dependency + +Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)): + +```Groovy +implementation "org.mozilla.components:support-test:{latest-version}" +``` + +## License + + 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/ diff --git a/mobile/android/android-components/components/support/test/build.gradle b/mobile/android/android-components/components/support/test/build.gradle new file mode 100644 index 0000000000..6111218d8c --- /dev/null +++ b/mobile/android/android-components/components/support/test/build.gradle @@ -0,0 +1,58 @@ +/* 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/. */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + defaultConfig { + minSdkVersion config.minSdkVersion + compileSdk config.compileSdkVersion + targetSdkVersion config.targetSdkVersion + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt') + } + } + + lint { + // Disabled since this caused issues with Android Gradle Plugin 3.2.1+ (NullPointerException:InvalidPackageDetector) + tasks.lint.enabled = false + + lintConfig file("lint.xml") + } + + namespace 'mozilla.components.support.test' +} + +dependencies { + implementation ComponentsDependencies.kotlin_coroutines + implementation ComponentsDependencies.kotlin_reflect + + implementation ComponentsDependencies.androidx_test_junit + api ComponentsDependencies.testing_mockito + implementation ComponentsDependencies.testing_coroutines + implementation ComponentsDependencies.androidx_fragment + implementation (ComponentsDependencies.testing_robolectric) { + exclude group: 'org.apache.maven' + } + implementation project(':support-base') + testImplementation ComponentsDependencies.testing_maven_ant_tasks + + implementation ComponentsDependencies.androidx_test_core + + testImplementation ComponentsDependencies.androidx_core + testImplementation ComponentsDependencies.androidx_test_junit + testImplementation project(':support-ktx') +} + +apply from: '../../../android-lint.gradle' +apply from: '../../../publish.gradle' +ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description) + diff --git a/mobile/android/android-components/components/support/test/lint.xml b/mobile/android/android-components/components/support/test/lint.xml new file mode 100644 index 0000000000..3c7a84d2fb --- /dev/null +++ b/mobile/android/android-components/components/support/test/lint.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- 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/. --> +<lint> + <issue id="InvalidPackage"> + <ignore path="**/byte-buddy-agent-*.jar"/> + <ignore path="**/bcprov-*on-*.jar"/> + <ignore path="**/shadows-framework-*.jar"/> + <ignore path="**/xstream-*.jar"/> + <ignore path="**/resources-4.0-*.jar"/> + </issue> +</lint> diff --git a/mobile/android/android-components/components/support/test/src/main/AndroidManifest.xml b/mobile/android/android-components/components/support/test/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..41078a7325 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ +<!-- 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/. --> +<manifest /> diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Expect.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Expect.kt new file mode 100644 index 0000000000..eae9878667 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Expect.kt @@ -0,0 +1,22 @@ +/* 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 mozilla.components.support.test + +import org.junit.Assert.fail +import kotlin.reflect.KClass + +/** + * Expect [block] to throw an exception. Otherwise fail the test (junit). + */ +inline fun <reified T : Throwable> expectException(clazz: KClass<T>, block: () -> Unit) { + try { + block() + fail("Expected exception to be thrown: $clazz") + } catch (e: Throwable) { + if (e !is T) { + throw e + } + } +} diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/KArgumentCaptor.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/KArgumentCaptor.kt new file mode 100644 index 0000000000..6b1b6bba52 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/KArgumentCaptor.kt @@ -0,0 +1,44 @@ +/* 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 mozilla.components.support.test + +import org.mockito.ArgumentCaptor +import kotlin.reflect.KClass + +/** + * Creates a [KArgumentCaptor] for given type. + */ +inline fun <reified T : Any> argumentCaptor(): KArgumentCaptor<T> { + return KArgumentCaptor(ArgumentCaptor.forClass(T::class.java), T::class) +} +class KArgumentCaptor<out T : Any?>( + private val captor: ArgumentCaptor<T>, + private val tClass: KClass<*>, +) { + + /** + * The first captured value of the argument. + * @throws IndexOutOfBoundsException if the value is not available. + */ + val value: T + get() = captor.value + + val allValues: List<T> + get() = captor.allValues + + @Suppress("UNCHECKED_CAST") + fun capture(): T { + return captor.capture() ?: castNull() + } +} + +/** + * Uses a quirk in the bytecode generated by Kotlin + * to cast [null] to a non-null type. + * + * See https://youtrack.jetbrains.com/issue/KT-8135. + */ +@Suppress("UNCHECKED_CAST") +private fun <T> castNull(): T = null as T diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Matchers.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Matchers.kt new file mode 100644 index 0000000000..b8d8f259ec --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Matchers.kt @@ -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 mozilla.components.support.test + +import org.mockito.AdditionalMatchers +import org.mockito.ArgumentCaptor +import org.mockito.Mockito + +/** + * Mockito matcher that matches <strong>anything</strong>, including nulls and varargs. + * + * (The version from Mockito doesn't work correctly with Kotlin code.) + */ +fun <T> any(): T { + Mockito.any<T>() + return uninitialized() +} + +/** + * Mockito matcher that matches if the argument is the same as the provided value. + * + * (The version from Mockito doesn't work correctly with Kotlin code.) + */ +fun <T> eq(value: T): T { + return Mockito.eq(value) ?: value +} + +/** + * Mockito matcher that matches if the argument is not the same as the provided value. + * + * (The version from Mockito doesn't work correctly with Kotlin code.) + */ +fun <T> not(value: T): T { + return AdditionalMatchers.not(value) ?: value +} + +/** + * Mockito matcher that captures the passed argument. + * + * (The version from Mockito doesn't work correctly with Kotlin code.) + */ +fun <T> capture(value: ArgumentCaptor<T>): T { + value.capture() + return uninitialized() +} + +/** + * Mockito matcher that matches <strong>anything</strong> as nullable. + * + * (The version from Mockito doesn't work correctly with Kotlin code.) + */ +inline fun <reified T> nullable(): T { + Mockito.nullable(T::class.java) + return uninitialized() +} + +@Suppress("UNCHECKED_CAST") +fun <T> uninitialized(): T = null as T diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Mock.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Mock.kt new file mode 100644 index 0000000000..38943c7577 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Mock.kt @@ -0,0 +1,54 @@ +/* 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 mozilla.components.support.test + +import android.view.MotionEvent +import org.mockito.Mockito + +/** + * Dynamically create a mock object. This method is helpful when creating mocks of classes + * using generics. + * + * Optional @param setup will be called on the mock after init. + * + * Instead of: + * <code>val foo = Mockito.mock(....Class of Bar<Baz>?...)<code> + * + * You can just use: + * <code>val foo: Bar<Baz> = mock()</code> + */ +inline fun <reified T : Any> mock(noinline setup: (T.() -> Unit)? = null): T = Mockito.mock(T::class.java)!! + .apply { setup?.invoke(this) } + +/** + * Equivalent to [mock] but allows inline setup of suspending functions. + */ +suspend inline fun <reified T : Any> coMock(noinline setup: (suspend T.() -> Unit)? = null): T = Mockito.mock(T::class.java)!! + .apply { setup?.invoke(this) } + +/** + * Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called. + * + * Alias for [Mockito.when ]. + * + * Taken from [mockito-kotlin](https://github.com/nhaarman/mockito-kotlin/). + */ +@Suppress("NOTHING_TO_INLINE") +inline fun <T> whenever(methodCall: T) = Mockito.`when`(methodCall)!! + +/** + * Creates a custom [MotionEvent] for testing. As of SDK 28 [MotionEvent]s can't be mocked anymore and need to be created + * through [MotionEvent.obtain]. + */ +fun mockMotionEvent( + action: Int, + downTime: Long = System.currentTimeMillis(), + eventTime: Long = System.currentTimeMillis(), + x: Float = 0f, + y: Float = 0f, + metaState: Int = 0, +): MotionEvent { + return MotionEvent.obtain(downTime, eventTime, action, x, y, metaState) +} diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ThrowProperty.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ThrowProperty.kt new file mode 100644 index 0000000000..fede1bf584 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ThrowProperty.kt @@ -0,0 +1,19 @@ +/* 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 mozilla.components.support.test + +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * A [ReadWriteProperty] implementation for creating stub properties. + */ +class ThrowProperty<T> : ReadWriteProperty<Any, T> { + override fun getValue(thisRef: Any, property: KProperty<*>): T = + throw UnsupportedOperationException("Cannot get $property") + + override fun setValue(thisRef: Any, property: KProperty<*>, value: T) = + throw UnsupportedOperationException("Cannot set $property") +} diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ext/Context.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ext/Context.kt new file mode 100644 index 0000000000..94e070eda7 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ext/Context.kt @@ -0,0 +1,19 @@ +/* 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 mozilla.components.support.test.ext + +import android.content.Context +import androidx.annotation.VisibleForTesting +import androidx.appcompat.R +import androidx.appcompat.view.ContextThemeWrapper +import mozilla.components.support.test.robolectric.testContext + +/** + * `testContext` wrapped with AppCompat theme. + * + * Useful for views that uses theme attributes, for example. + */ +@VisibleForTesting val appCompatContext: Context + get() = ContextThemeWrapper(testContext, R.style.Theme_AppCompat) diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ext/Job.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ext/Job.kt new file mode 100644 index 0000000000..a1f8f80062 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ext/Job.kt @@ -0,0 +1,15 @@ +/* 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 mozilla.components.support.test.ext + +import kotlinx.coroutines.Job +import kotlinx.coroutines.runBlocking + +/** + * Blocks the current thread until the job is complete. + */ +fun Job.joinBlocking() { + runBlocking { join() } +} diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ext/KProperty.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ext/KProperty.kt new file mode 100644 index 0000000000..cf27945f54 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/ext/KProperty.kt @@ -0,0 +1,29 @@ +/* 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 mozilla.components.support.test.ext + +import kotlin.reflect.KProperty0 +import kotlin.reflect.jvm.isAccessible + +/** + * Returns true if a lazy property has been initialized, or if the property is not lazy. + * + * implementation inspired by https://stackoverflow.com/a/42536189 + */ +val KProperty0<*>.isLazyInitialized: Boolean + get() { + // Prevent exception for accessing private getDelegate function. + val originalAccessLevel = isAccessible + isAccessible = true + + val lazyDelegate = getDelegate() + require(lazyDelegate is Lazy<*>) { "Expected receiver property to be lazy" } + + val isLazyInitialized = lazyDelegate.isInitialized() + + // Reset access level. + isAccessible = originalAccessLevel + return isLazyInitialized + } diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/fakes/FakeClock.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/fakes/FakeClock.kt new file mode 100644 index 0000000000..0411873234 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/fakes/FakeClock.kt @@ -0,0 +1,19 @@ +/* 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 mozilla.components.support.test.fakes + +/** + * A fake clock exposing a [time] property that can be changed at test runtime. + * + * Ideally a class needing time hides the calls to System.currentTimeMillis() behind a lambda + * that can it can invoke. In a test this lambda can be replaced by FakeClock()::time. + */ +class FakeClock( + var time: Long = 0, +) { + fun advanceBy(time: Long) { + this.time += time + } +} diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/file/Resources.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/file/Resources.kt new file mode 100644 index 0000000000..2d5e842731 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/file/Resources.kt @@ -0,0 +1,19 @@ +/* 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 mozilla.components.support.test.file + +import org.junit.Assert + +/** + * Loads a file from the resources folder and returns its content as a string object. + * @param path The path where the file is located + */ +fun Any.loadResourceAsString(path: String): String { + return javaClass.getResourceAsStream(path)!!.bufferedReader().use { + it.readText() + }.also { + Assert.assertNotNull(it) + } +} diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/Extensions.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/Extensions.kt new file mode 100644 index 0000000000..3b01aabdcd --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/Extensions.kt @@ -0,0 +1,14 @@ +/* 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 mozilla.components.support.test.robolectric + +import android.content.Context +import androidx.test.core.app.ApplicationProvider + +/** + * Provides application context for test purposes + */ +inline val testContext: Context + get() = ApplicationProvider.getApplicationContext() diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/Fragments.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/Fragments.kt new file mode 100644 index 0000000000..6ad1d3d526 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/Fragments.kt @@ -0,0 +1,32 @@ +/* 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 mozilla.components.support.test.robolectric + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import org.robolectric.Robolectric + +/** + * Set up an added [Fragment] to a [FragmentActivity] that has been initialized to a resumed state. + * + * @param fragmentTag the name that will be used to tag the fragment inside the [FragmentManager]. + * @param fragmentFactory a lambda function that returns a Fragment that will be added to the Activity. + * + * @return The same [Fragment] that was returned from [fragmentFactory] after it got added to the + * Activity. + */ +inline fun <T : Fragment> createAddedTestFragment(fragmentTag: String = "test", fragmentFactory: () -> T): T { + val activity = Robolectric.buildActivity(FragmentActivity::class.java) + .create() + .start() + .resume() + .get() + + return fragmentFactory().also { + activity.supportFragmentManager.beginTransaction() + .add(it, fragmentTag) + .commitNow() + } +} diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/Permissions.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/Permissions.kt new file mode 100644 index 0000000000..606956631a --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/Permissions.kt @@ -0,0 +1,21 @@ +/* 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 mozilla.components.support.test.robolectric + +import android.app.Application +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import org.robolectric.Shadows.shadowOf + +/** + * A helper for working with permission + * just pass one or more permission that you need to be granted. + * @param permissions list of permissions that you need to be granted. + */ +fun grantPermission(vararg permissions: String) { + val application = shadowOf(getApplicationContext<Application>()) + permissions.map { + application.grantPermissions(it) + } +} diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/shadow/PixelCopyShadow.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/shadow/PixelCopyShadow.kt new file mode 100644 index 0000000000..45b6c86d71 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/robolectric/shadow/PixelCopyShadow.kt @@ -0,0 +1,41 @@ +/* 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 mozilla.components.support.test.robolectric.shadow + +import android.annotation.TargetApi +import android.graphics.Bitmap +import android.graphics.Rect +import android.os.Build +import android.os.Handler +import android.view.PixelCopy +import android.view.Window +import org.robolectric.annotation.Implementation +import org.robolectric.annotation.Implements + +/** + * Shadow for [PixelCopy] API. + */ +@Implements(PixelCopy::class, minSdk = Build.VERSION_CODES.N) +@TargetApi(Build.VERSION_CODES.N) +class PixelCopyShadow { + + companion object { + var copyResult = PixelCopy.SUCCESS + + @JvmStatic + @Implementation + // Some parameters are unused but method signature should be the same as for original class. + @Suppress("UNUSED_PARAMETER") + fun request( + source: Window, + srcRect: Rect?, + dest: Bitmap, + listener: PixelCopy.OnPixelCopyFinishedListener, + listenerThread: Handler?, + ) { + listener.onPixelCopyFinished(copyResult) + } + } +} diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/rule/Helpers.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/rule/Helpers.kt new file mode 100644 index 0000000000..17b3bbf9e6 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/rule/Helpers.kt @@ -0,0 +1,57 @@ +/* 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/. */ + +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // for TestMainDispatcher + +package mozilla.components.support.test.rule + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestResult +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.internal.TestMainDispatcher +import kotlinx.coroutines.test.runTest +import kotlin.reflect.full.companionObject +import kotlin.reflect.full.companionObjectInstance +import kotlin.reflect.full.memberProperties +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +/** + * `coroutines.test` default timeout to use when waiting for asynchronous completions of the coroutines + * managed by a [TestCoroutineScheduler]. + */ +private val DEFAULT_DISPATCH_TIMEOUT_SECONDS = 60.seconds + +/** + * Convenience method of executing [testBody] in a new coroutine running with the + * [TestDispatcher] previously set through [Dispatchers.setMain]. + * + * Running a test with a shared [TestDispatcher] allows + * - newly created coroutines inside it will automatically be reparented to the test coroutine context. + * - leveraging an already set strategy for entering launch / async blocks. + * - easier scheduling control. + * + * @see runTest + * @see Dispatchers.setMain + * @see TestDispatcher + */ +fun runTestOnMain( + testTimeoutMs: Duration = DEFAULT_DISPATCH_TIMEOUT_SECONDS, + testBody: suspend TestScope.() -> Unit, +): TestResult { + val mainDispatcher = Dispatchers.Main + require(mainDispatcher is TestMainDispatcher) { + "A TestDispatcher is not available. Use MainCoroutineRule or Dispatchers.setMain to set one before calling this method" + } + + // Get the TestDispatcher set through `Dispatchers.setMain(..)`. + val companionObject = mainDispatcher::class.companionObject + val companionInstance = mainDispatcher::class.companionObjectInstance + val testDispatcher = companionObject!!.memberProperties.first().getter.call(companionInstance) as TestDispatcher + + // Delegate to the original implementation of `runTest`. Just with a previously set TestDispatcher. + runTest(testDispatcher, timeout = testTimeoutMs, testBody) +} diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/rule/MainCoroutineRule.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/rule/MainCoroutineRule.kt new file mode 100644 index 0000000000..b8df5e7ef6 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/rule/MainCoroutineRule.kt @@ -0,0 +1,56 @@ +/* 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 mozilla.components.support.test.rule + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import mozilla.components.support.base.utils.NamedThreadFactory +import org.junit.rules.TestWatcher +import org.junit.runner.Description +import java.util.concurrent.Executors + +/** + * Create single threaded dispatcher for test environment. + */ +@Deprecated("Use a `TestDispatcher` from the kotlinx-coroutines-test library", ReplaceWith("UnconfinedTestDispatcher()")) +fun createTestCoroutinesDispatcher(): CoroutineDispatcher = Executors.newSingleThreadExecutor( + NamedThreadFactory("TestCoroutinesDispatcher"), +).asCoroutineDispatcher() + +/** + * JUnit rule to change Dispatchers.Main in coroutines. + * This assumes no other calls to `Dispatchers.setMain` are made to override the main dispatcher. + * + * @param testDispatcher [TestDispatcher] for handling all coroutines execution. + * Defaults to [UnconfinedTestDispatcher] which will eagerly enter `launch` or `async` blocks. + */ +@OptIn(ExperimentalCoroutinesApi::class) +class MainCoroutineRule(val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()) : TestWatcher() { + /** + * Get a [TestScope] that integrates with `runTest` and can be passed as an argument + * to the code under test when a [CoroutineScope] is required. + * + * This will rely on [testDispatcher] for controlling entering `launch` or `async` blocks. + */ + val scope by lazy { TestScope(testDispatcher) } + + override fun starting(description: Description) { + Dispatchers.setMain(testDispatcher) + super.starting(description) + } + + override fun finished(description: Description) { + Dispatchers.resetMain() + super.finished(description) + } +} diff --git a/mobile/android/android-components/components/support/test/src/test/java/PermissionsTest.kt b/mobile/android/android-components/components/support/test/src/test/java/PermissionsTest.kt new file mode 100644 index 0000000000..1182821787 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/test/java/PermissionsTest.kt @@ -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 mozilla.components.support.test.robolectric + +import android.Manifest.permission.INTERNET +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.support.ktx.android.content.isPermissionGranted +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class PermissionsTest { + + @Test + fun `after call grantPermission this permission must be granted `() { + var isGranted = testContext.isPermissionGranted(INTERNET) + assertFalse(isGranted) + + grantPermission(INTERNET) + isGranted = testContext.isPermissionGranted(INTERNET) + + assertTrue(isGranted) + } +} diff --git a/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/ThrowPropertyTest.kt b/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/ThrowPropertyTest.kt new file mode 100644 index 0000000000..24e50c2b6d --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/ThrowPropertyTest.kt @@ -0,0 +1,24 @@ +/* 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 mozilla.components.support.test.robolectric.mozilla.components.support.test + +import mozilla.components.support.test.ThrowProperty +import org.junit.Test + +class ThrowPropertyTest { + private val testProperty = "test" + + @Test(expected = UnsupportedOperationException::class) + fun `exception thrown when get value is called`() { + val throwProperty = ThrowProperty<String>() + throwProperty.getValue(testProperty, ::testProperty) + } + + @Test(expected = UnsupportedOperationException::class) + fun `exception thrown when set value is called`() { + val throwProperty = ThrowProperty<String>() + throwProperty.setValue(testProperty, ::testProperty, "test1") + } +} diff --git a/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/file/ResourcesTest.kt b/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/file/ResourcesTest.kt new file mode 100644 index 0000000000..58cd279fcd --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/file/ResourcesTest.kt @@ -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 mozilla.components.support.test.robolectric.mozilla.components.support.test.file + +import mozilla.components.support.test.file.loadResourceAsString +import org.junit.Assert.assertEquals +import org.junit.Test + +class ResourcesTest { + + @Test + fun getProvidedAppContext() { + assertEquals("42", loadResourceAsString("/example_file.txt")) + } +} diff --git a/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/robolectric/ExtensionsTest.kt b/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/robolectric/ExtensionsTest.kt new file mode 100644 index 0000000000..6b86a649a5 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/robolectric/ExtensionsTest.kt @@ -0,0 +1,23 @@ +/* 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 mozilla.components.support.test.robolectric + +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ExtensionsTest { + + @Test + fun getProvidedAppContext() { + Assert.assertEquals( + ApplicationProvider.getApplicationContext(), + testContext, + ) + } +} diff --git a/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/robolectric/FragmentsTest.kt b/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/robolectric/FragmentsTest.kt new file mode 100644 index 0000000000..eca68aa7e7 --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/test/java/mozilla/components/support/test/robolectric/FragmentsTest.kt @@ -0,0 +1,30 @@ +/* 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 mozilla.components.support.test.robolectric + +import androidx.fragment.app.Fragment +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class FragmentsTest { + + @Test + fun `setupFragment should add fragment correctly`() { + val addedFragment = createAddedTestFragment { Fragment() } + + assertTrue(addedFragment.isAdded) + } + + @Test + fun `setupFragment should add fragment with correct tag`() { + val fragment = createAddedTestFragment(fragmentTag = "aTag") { Fragment() } + + assertNotNull(fragment.parentFragmentManager.findFragmentByTag("aTag")) + } +} diff --git a/mobile/android/android-components/components/support/test/src/test/resources/example_file.txt b/mobile/android/android-components/components/support/test/src/test/resources/example_file.txt new file mode 100644 index 0000000000..f70d7bba4a --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/test/resources/example_file.txt @@ -0,0 +1 @@ +42
\ No newline at end of file diff --git a/mobile/android/android-components/components/support/test/src/test/resources/robolectric.properties b/mobile/android/android-components/components/support/test/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..932b01b9eb --- /dev/null +++ b/mobile/android/android-components/components/support/test/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +sdk=28 |