From c41ea5254aa5e10301060f5c7fedaad8156184f3 Mon Sep 17 00:00:00 2001 From: dylan-muszel <42148418+dylan-muszel@users.noreply.github.com> Date: Wed, 8 Jan 2020 17:59:34 -0300 Subject: [PATCH] Adding tests (#64) * Kotlin Migration - Adapter and DI (#50) * Adapter * DI * Converting object to class * Fix pair * Migrate BasePresenter * Migrate LceView and add copyright to BasePresenter * Migrate LeView * Migrate BasePresenterTest * Add deprecated to runIfViewAttached, replace apply for run in tests, clean code * Migration of permissionsManager * Migration of permissionListener * Fix redundant private set * Refactor to clean code * More refactor * Kotlin migration utils (#52) * Keyboard * Logger * Shared preferences * Migration (#53) * WolmoActivityHandler converted to kotlin (#51) * WolmoActivityHandler converted to kotlin * WolmoActivityHandler converted to kotlin * WolmoActivityHandler converted to kotlin * Kotlin migration fragments (#56) * FRagments migrated * Restoring GetImage.java * Restoring java getimage test * To spaces * Indentation * Reformat * Code reformats * fixing tests * Reverting get image fragment * changing imports * Last migration (#58) * FRagments migrated * Restoring GetImage.java * Restoring java getimage test * To spaces * Indentation * Reformat * Code reformats * fixing tests * Last migration * Kotlin migration fixes - update versions (#59) * Fixing overriding base presenter * Presenter as val * Fix tests * Get presnter instead of require * Fixing wolmo fragment inject * DefaultModule as class * Shared preferences string nullable * Android core as api * Reverting modules * Updating version * Updating versions * Fixing tests * Using implmentation * API insteda of implementation revert * Rollback handler * Also adding some minor changes * Another minor change * Updating comments and using requireArgument * Handle arguments nullable on fragment * Fixing url * Adding coroutine base presenter * Coroutines and more * updating get image fragment * Fixing doc * Updating get image fragment * Rollback handler and minor changes (#61) * Rollback handler * Also adding some minor changes * Another minor change * Updating comments and using requireArgument * Handle arguments nullable on fragment * Fixing url * Removing wrong comment * Updating get image * Moving all get image stuff to a helper * Fixing issues * Some fixes * Removing wrong test * Serializable * Importing * Fixing * Reverting image provider * Provider change * More fixes * Removing wrong arguments * More navigation utils * Updating kapt * Removing getimagekt * Reverting get image fragment * Reverting get image fragment test * Reverting spaces * More reverts * More reverts * Reverting tests * More test reverts * Minor fixes * More comments * Removing log * Single lines * Removing imports * Updating gradle * More tests * Features readme * Reformat * View extensions readme * Adding tests * Typo * Removing wrong startactivity Co-authored-by: igsosa92 <43349271+igsosa92@users.noreply.github.com> Co-authored-by: patofernandez <49655621+patofernandez@users.noreply.github.com> --- FEATURES.md | 8 +++ core/build.gradle | 2 +- .../wolmo/core/tests/CoroutineTestRule.kt | 48 ++++++++++++++++ .../wolmo/core/tests/WolmoPresenterTest.kt | 22 +++---- .../wolox/wolmo/core/util/NavigationUtils.kt | 9 +-- .../wolmo/core/tests/CoroutineTestRuleTest.kt | 37 ++++++++++++ .../core/tests/WolmoPresenterTestTest.kt | 57 +++++++++++++++++++ 7 files changed, 162 insertions(+), 21 deletions(-) create mode 100644 core/src/main/java/ar/com/wolox/wolmo/core/tests/CoroutineTestRule.kt create mode 100644 core/src/test/java/ar/com/wolox/wolmo/core/tests/CoroutineTestRuleTest.kt create mode 100644 core/src/test/java/ar/com/wolox/wolmo/core/tests/WolmoPresenterTestTest.kt diff --git a/FEATURES.md b/FEATURES.md index 237db95..89da679 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -87,3 +87,11 @@ Base implementation of a `DaggerFragment` that is MVP-ready (you can access the It also provides this extra feature: - `requireArgument(key: String): T`: get argument by the given [key] and returns it as a non-null [T]. + +# Wolmo testing features + +### WolmoPresenterTest +A base that setups the environment for a Wolmo's [BasePresenter] test. It also provides a prepared environment for annotated mocks. + +### CoroutineTestRule +A Junit Test Rule that allows to use Coroutines main dispatcher on a test. If [runOnAllTests] is false then all tests will have this configuration, otherwise just those that have [CoroutineTest] annotation. diff --git a/core/build.gradle b/core/build.gradle index 9dc988e..8931283 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -82,7 +82,7 @@ dependencies { // Coroutines api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" // Test testImplementation "junit:junit:$junit_version" diff --git a/core/src/main/java/ar/com/wolox/wolmo/core/tests/CoroutineTestRule.kt b/core/src/main/java/ar/com/wolox/wolmo/core/tests/CoroutineTestRule.kt new file mode 100644 index 0000000..3b31b68 --- /dev/null +++ b/core/src/main/java/ar/com/wolox/wolmo/core/tests/CoroutineTestRule.kt @@ -0,0 +1,48 @@ +package ar.com.wolox.wolmo.core.tests + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description +import java.util.concurrent.Executors + +/** + * If a test method is annotated with this, then the [CoroutineTestRule] will be executed. + * Use it when [CoroutineTestRule.runOnAllTests] is false. + */ +annotation class CoroutineTest + +/** + * A Junit Test Rule that allows to use Coroutines main dispatcher on a test. + * If [runOnAllTests] is false then all tests will have this configuration, + * otherwise just those that have [CoroutineTest] annotation. + */ +@ExperimentalCoroutinesApi +class CoroutineTestRule(private val runOnAllTests: Boolean = false) : TestWatcher() { + + private val mainThreadSurrogate = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + + private fun isCoroutineTest(description: Description?): Boolean { + return description?.annotations?.filterIsInstance()?.isNotEmpty() == true + } + + private fun shouldRunRule(description: Description?): Boolean { + return runOnAllTests || isCoroutineTest(description) + } + + override fun starting(description: Description?) { + if (shouldRunRule(description)) { + Dispatchers.setMain(mainThreadSurrogate) + } + } + + override fun finished(description: Description?) { + if (shouldRunRule(description)) { + Dispatchers.resetMain() + mainThreadSurrogate.close() + } + } +} diff --git a/core/src/main/java/ar/com/wolox/wolmo/core/tests/WolmoPresenterTest.kt b/core/src/main/java/ar/com/wolox/wolmo/core/tests/WolmoPresenterTest.kt index 88ebca9..633f0ad 100644 --- a/core/src/main/java/ar/com/wolox/wolmo/core/tests/WolmoPresenterTest.kt +++ b/core/src/main/java/ar/com/wolox/wolmo/core/tests/WolmoPresenterTest.kt @@ -1,47 +1,41 @@ package ar.com.wolox.wolmo.core.tests import ar.com.wolox.wolmo.core.presenter.BasePresenter +import org.junit.After import org.junit.Before import org.mockito.Mockito +import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations import java.lang.reflect.ParameterizedType /** * A base that setups the environment for a Wolmo's [BasePresenter] test. + * It also provides a prepared environment for annotated mocks. */ abstract class WolmoPresenterTest> { - /** - * The presenter to be tested. - */ + /** The presenter to be tested */ protected lateinit var presenter: P - /** - * The mocked view that the presenter will use. - */ - lateinit var view: V + /** The mocked view attached to the presenter. */ + protected lateinit var view: V @Suppress("UNCHECKED_CAST") private fun getViewClass(): Class { return (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class } - /** - * Setup the environment. - */ + /** Setup the environment. */ @Before fun setupWolmoPresenterTest() { MockitoAnnotations.initMocks(this) presenter = getPresenterInstance() - view = Mockito.mock(getViewClass()) - presenter.attachView(view) + view = mock(getViewClass()).also { presenter.attachView(it) } } /** * This method provides the presenter instance that will be tested. * If the presenter should be spied, it's possible to return the presenter spied with [Mockito.spy]. - * - * @return the [BasePresenter] instance. */ abstract fun getPresenterInstance(): P } \ No newline at end of file diff --git a/core/src/main/java/ar/com/wolox/wolmo/core/util/NavigationUtils.kt b/core/src/main/java/ar/com/wolox/wolmo/core/util/NavigationUtils.kt index e3368c3..ef78b4d 100644 --- a/core/src/main/java/ar/com/wolox/wolmo/core/util/NavigationUtils.kt +++ b/core/src/main/java/ar/com/wolox/wolmo/core/util/NavigationUtils.kt @@ -81,17 +81,14 @@ fun Context.makeCall(phone: String) { } /** - * Sends an intent to start an [Activity] for the provided [clazz] from a [context] + * Sends an intent to start an [Activity] for the provided [clazz] from a [Context] * with a variable number of instances of [intentExtras] that will be sent as extras. */ @SafeVarargs -fun Context.jumpTo( - clazz: Class<*>, - vararg intentExtras: IntentExtra -) = jumpTo(clazz, null, *intentExtras) +fun Context.jumpTo(clazz: Class<*>, vararg intentExtras: IntentExtra) = jumpTo(clazz, null, *intentExtras) /** - * Sends an intent to start an [Activity] for the provided [clazz] from a [context] + * Sends an intent to start an [Activity] for the provided [clazz] from a [Context] * with a variable number of instances of [intentExtras] that will be sent as extras. * It accepts a [transition] that defines the animation behaviour. */ diff --git a/core/src/test/java/ar/com/wolox/wolmo/core/tests/CoroutineTestRuleTest.kt b/core/src/test/java/ar/com/wolox/wolmo/core/tests/CoroutineTestRuleTest.kt new file mode 100644 index 0000000..7afb8c2 --- /dev/null +++ b/core/src/test/java/ar/com/wolox/wolmo/core/tests/CoroutineTestRuleTest.kt @@ -0,0 +1,37 @@ +package ar.com.wolox.wolmo.core.tests + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import org.junit.Rule +import org.junit.Test + +@ExperimentalCoroutinesApi +class CoroutineTestRuleTest { + + @get:Rule + val coroutineTestRule = CoroutineTestRule(runOnAllTests = true) + + @Test(expected = Test.None::class /* no exception expected */) + fun `given a rule with run on all tests true when run coroutine on main then there's no exception thrown`() { + runBlocking(Dispatchers.Main) {} + } +} + +@ExperimentalCoroutinesApi +class CoroutineTestRuleTest2 { + + @get:Rule + val coroutineTestRule = CoroutineTestRule(runOnAllTests = false) + + @Test(expected = IllegalStateException::class) + fun `given a rule with run on all tests false when run coroutine on main then a IllegalStateException is thrown`() { + runBlocking(Dispatchers.Main) {} + } + + @Test(expected = Test.None::class /* no exception expected */) + @CoroutineTest + fun `given a rule with run on all tests false and annotated with CoroutineScope when run coroutine on main then there's no exception thrown`() { + runBlocking(Dispatchers.Main) {} + } +} \ No newline at end of file diff --git a/core/src/test/java/ar/com/wolox/wolmo/core/tests/WolmoPresenterTestTest.kt b/core/src/test/java/ar/com/wolox/wolmo/core/tests/WolmoPresenterTestTest.kt new file mode 100644 index 0000000..4d08374 --- /dev/null +++ b/core/src/test/java/ar/com/wolox/wolmo/core/tests/WolmoPresenterTestTest.kt @@ -0,0 +1,57 @@ +package ar.com.wolox.wolmo.core.tests + +import android.graphics.Rect +import ar.com.wolox.wolmo.core.presenter.BasePresenter +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.notNullValue +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +class WolmoPresenterTestTest : WolmoPresenterTest() { + + @Mock + lateinit var rectMocked: Rect + + override fun getPresenterInstance() = TestPresenter() + + private fun tryToGet(getter: () -> T): T? = try { + getter() + } catch (ignored: Exception) { + null + } + + @Test + fun `given a test when getting instances then those are not null`() { + + val presenter = tryToGet { presenter } + val view = tryToGet { view } + + assertThat(presenter, `is`(notNullValue())) + assertThat(view, `is`(notNullValue())) + } + + @Test + fun `given an annotated mock when using it then it's not null`() { + + val rectMocked = tryToGet { rectMocked } + + assertThat(rectMocked, `is`(notNullValue())) + } + + @Test + fun `given a presenter and a view when getting presenter get a call view request then it calls the view`() { + presenter.onCallViewRequest() + verify(view, times(1)).doSomething() + } + + interface TestView { + fun doSomething() + } + + class TestPresenter : BasePresenter() { + fun onCallViewRequest() = view?.doSomething() + } +} \ No newline at end of file