Issue#67 Extract JUnit5 MainDispatcher
This commit is contained in:
parent
3b96a5d9eb
commit
7e019973e8
11 changed files with 112 additions and 57 deletions
|
|
@ -2,7 +2,7 @@ package org.fnives.test.showcase.di
|
|||
|
||||
import android.content.Context
|
||||
import org.fnives.test.showcase.model.network.BaseUrl
|
||||
import org.fnives.test.showcase.testutils.TestMainDispatcher
|
||||
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher
|
||||
import org.fnives.test.showcase.ui.auth.AuthViewModel
|
||||
import org.fnives.test.showcase.ui.home.MainViewModel
|
||||
import org.fnives.test.showcase.ui.splash.SplashViewModel
|
||||
|
|
@ -20,7 +20,7 @@ import org.mockito.kotlin.doReturn
|
|||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
|
||||
@ExtendWith(TestMainDispatcher::class)
|
||||
@ExtendWith(StandardTestMainDispatcher::class)
|
||||
class DITest : KoinTest {
|
||||
|
||||
private val authViewModel by inject<AuthViewModel>()
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
package org.fnives.test.showcase.testutils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.fnives.test.showcase.testutils.TestMainDispatcher.Companion.testDispatcher
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
|
||||
/**
|
||||
* Custom Junit5 Extension which replaces the main dispatcher with a [TestDispatcher]
|
||||
*
|
||||
* One can access the test dispatcher via [testDispatcher] static getter.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class TestMainDispatcher : BeforeEachCallback, AfterEachCallback {
|
||||
|
||||
override fun beforeEach(context: ExtensionContext?) {
|
||||
val testDispatcher = StandardTestDispatcher()
|
||||
privateTestDispatcher = testDispatcher
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
}
|
||||
|
||||
override fun afterEach(context: ExtensionContext?) {
|
||||
Dispatchers.resetMain()
|
||||
privateTestDispatcher = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var privateTestDispatcher: TestDispatcher? = null
|
||||
val testDispatcher: TestDispatcher
|
||||
get() = privateTestDispatcher
|
||||
?: throw IllegalStateException("TestMainDispatcher is in afterEach State")
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import org.fnives.test.showcase.core.login.LoginUseCase
|
|||
import org.fnives.test.showcase.model.auth.LoginCredentials
|
||||
import org.fnives.test.showcase.model.auth.LoginStatus
|
||||
import org.fnives.test.showcase.model.shared.Answer
|
||||
import org.fnives.test.showcase.testutils.TestMainDispatcher
|
||||
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher
|
||||
import org.fnives.test.showcase.ui.shared.Event
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
|
|
@ -27,13 +27,13 @@ import org.mockito.kotlin.whenever
|
|||
import java.util.stream.Stream
|
||||
|
||||
@Suppress("TestFunctionName")
|
||||
@ExtendWith(InstantExecutorExtension::class, TestMainDispatcher::class)
|
||||
@ExtendWith(InstantExecutorExtension::class, StandardTestMainDispatcher::class)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class AuthViewModelTest {
|
||||
|
||||
private lateinit var sut: AuthViewModel
|
||||
private lateinit var mockLoginUseCase: LoginUseCase
|
||||
private val testScheduler get() = TestMainDispatcher.testDispatcher.scheduler
|
||||
private val testScheduler get() = StandardTestMainDispatcher.testDispatcher.scheduler
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
|
|
|
|||
|
|
@ -3,20 +3,20 @@ package org.fnives.test.showcase.ui.auth
|
|||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import org.fnives.test.showcase.android.testutil.InstantExecutorExtension
|
||||
import org.fnives.test.showcase.core.login.LoginUseCase
|
||||
import org.fnives.test.showcase.testutils.TestMainDispatcher
|
||||
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.kotlin.mock
|
||||
|
||||
@ExtendWith(InstantExecutorExtension::class, TestMainDispatcher::class)
|
||||
@ExtendWith(InstantExecutorExtension::class, StandardTestMainDispatcher::class)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class CodeKataAuthViewModel {
|
||||
|
||||
private lateinit var sut: AuthViewModel
|
||||
private lateinit var mockLoginUseCase: LoginUseCase
|
||||
private val testScheduler get() = TestMainDispatcher.testDispatcher.scheduler
|
||||
private val testScheduler get() = StandardTestMainDispatcher.testDispatcher.scheduler
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import org.fnives.test.showcase.model.content.ContentId
|
|||
import org.fnives.test.showcase.model.content.FavouriteContent
|
||||
import org.fnives.test.showcase.model.content.ImageUrl
|
||||
import org.fnives.test.showcase.model.shared.Resource
|
||||
import org.fnives.test.showcase.testutils.TestMainDispatcher
|
||||
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
|
|
@ -29,7 +29,7 @@ import org.mockito.kotlin.verifyNoMoreInteractions
|
|||
import org.mockito.kotlin.whenever
|
||||
|
||||
@Suppress("TestFunctionName")
|
||||
@ExtendWith(InstantExecutorExtension::class, TestMainDispatcher::class)
|
||||
@ExtendWith(InstantExecutorExtension::class, StandardTestMainDispatcher::class)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class MainViewModelTest {
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ internal class MainViewModelTest {
|
|||
private lateinit var mockFetchContentUseCase: FetchContentUseCase
|
||||
private lateinit var mockAddContentToFavouriteUseCase: AddContentToFavouriteUseCase
|
||||
private lateinit var mockRemoveContentFromFavouritesUseCase: RemoveContentFromFavouritesUseCase
|
||||
private val testScheduler get() = TestMainDispatcher.testDispatcher.scheduler
|
||||
private val testScheduler get() = StandardTestMainDispatcher.testDispatcher.scheduler
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.jraska.livedata.test
|
|||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import org.fnives.test.showcase.android.testutil.InstantExecutorExtension
|
||||
import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase
|
||||
import org.fnives.test.showcase.testutils.TestMainDispatcher
|
||||
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher
|
||||
import org.fnives.test.showcase.ui.shared.Event
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
|
|
@ -14,13 +14,13 @@ import org.mockito.kotlin.doReturn
|
|||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
|
||||
@ExtendWith(InstantExecutorExtension::class, TestMainDispatcher::class)
|
||||
@ExtendWith(InstantExecutorExtension::class, StandardTestMainDispatcher::class)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class SplashViewModelTest {
|
||||
|
||||
private lateinit var mockIsUserLoggedInUseCase: IsUserLoggedInUseCase
|
||||
private lateinit var sut: SplashViewModel
|
||||
private val testScheduler get() = TestMainDispatcher.testDispatcher.scheduler
|
||||
private val testScheduler get() = StandardTestMainDispatcher.testDispatcher.scheduler
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ Our test class is `org.fnives.test.showcase.ui.splash.CodeKataSplashViewModelTes
|
|||
|
||||
To properly test LiveData we need to make them instant, meaning as soon as the value is set the observers are updated. To Do this we can use a `InstantExecutorExtension`.
|
||||
|
||||
Also We need to set MainDispatcher as TestDispatcher, for this we can use the `TestMainDispatcher` Extension.
|
||||
Also We need to set MainDispatcher as TestDispatcher, for this we can use the `StandardTestMainDispatcher` Extension.
|
||||
|
||||
To add this to our TestClass we need to do the following:
|
||||
|
||||
```kotlin
|
||||
@ExtendWith(InstantExecutorExtension::class, TestMainDispatcher::class)
|
||||
@ExtendWith(InstantExecutorExtension::class, StandardTestMainDispatcher::class)
|
||||
class CodeKataSplashViewModelTest {
|
||||
```
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ Next let's set up our System Under Test as usual:
|
|||
```kotlin
|
||||
private lateinit var mockIsUserLoggedInUseCase: IsUserLoggedInUseCase
|
||||
private lateinit var sut: SplashViewModel
|
||||
private val testScheduler get() = TestMainDispatcher.testDispatcher.scheduler // just a shortcut
|
||||
private val testScheduler get() = StandardTestMainDispatcher.testDispatcher.scheduler // just a shortcut
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
|
|
@ -69,7 +69,7 @@ val navigateToTestObserver = sut.navigateTo.test()
|
|||
|
||||
Since the action takes place in the ViewModel constructor, instead of additional calls, we need to simulate that time has elapsed.
|
||||
|
||||
Note: the `TestMainDispatcher` Extension we are using sets `StandardTestDispatcher` as the dispatcher for `Dispatcher.Main`, that's why our test is linear and not shaky.
|
||||
Note: the `StandardTestMainDispatcher` Extension we are using sets `StandardTestDispatcher` as the dispatcher for `Dispatcher.Main`, that's why our test is linear and not shaky.
|
||||
|
||||
```kotlin
|
||||
testScheduler.advanceTimeBy(501)
|
||||
|
|
|
|||
|
|
@ -32,4 +32,6 @@ android {
|
|||
dependencies {
|
||||
implementation "org.junit.jupiter:junit-jupiter-engine:$junit5_version"
|
||||
implementation "androidx.arch.core:core-runtime:$arch_core_version"
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package org.fnives.test.showcase.android.testutil
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestDispatcher
|
||||
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher.Companion.testDispatcher
|
||||
|
||||
/**
|
||||
* Custom Junit5 Extension which replaces the main dispatcher with a Standard [TestDispatcher]
|
||||
*
|
||||
* One can access the test dispatcher via [testDispatcher] static getter.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class StandardTestMainDispatcher : TestMainDispatcher() {
|
||||
|
||||
override var createdTestDispatcher: TestDispatcher?
|
||||
get() = privateTestDispatcher
|
||||
set(value) {
|
||||
privateTestDispatcher = value
|
||||
}
|
||||
|
||||
override fun createDispatcher(): TestDispatcher = StandardTestDispatcher()
|
||||
|
||||
companion object {
|
||||
private var privateTestDispatcher: TestDispatcher? = null
|
||||
val testDispatcher: TestDispatcher
|
||||
get() = privateTestDispatcher
|
||||
?: throw IllegalStateException("StandardTestMainDispatcher is in afterEach State")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package org.fnives.test.showcase.android.testutil
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
|
||||
/**
|
||||
* Custom Junit5 Extension which replaces the main dispatcher with a [TestDispatcher]
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
abstract class TestMainDispatcher : BeforeEachCallback, AfterEachCallback {
|
||||
|
||||
protected abstract var createdTestDispatcher: TestDispatcher?
|
||||
|
||||
abstract fun createDispatcher() : TestDispatcher
|
||||
|
||||
final override fun beforeEach(context: ExtensionContext?) {
|
||||
val testDispatcher = createDispatcher()
|
||||
createdTestDispatcher = testDispatcher
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
}
|
||||
|
||||
final override fun afterEach(context: ExtensionContext?) {
|
||||
Dispatchers.resetMain()
|
||||
createdTestDispatcher = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package org.fnives.test.showcase.android.testutil
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestDispatcher
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import org.fnives.test.showcase.android.testutil.UnconfinedTestMainDispatcher.Companion.testDispatcher
|
||||
|
||||
/**
|
||||
* Custom Junit5 Extension which replaces the main dispatcher with a Unconfined [TestDispatcher]
|
||||
*
|
||||
* One can access the test dispatcher via [testDispatcher] static getter.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class UnconfinedTestMainDispatcher : TestMainDispatcher() {
|
||||
|
||||
override var createdTestDispatcher: TestDispatcher?
|
||||
get() = privateTestDispatcher
|
||||
set(value) {
|
||||
privateTestDispatcher = value
|
||||
}
|
||||
|
||||
override fun createDispatcher(): TestDispatcher = UnconfinedTestDispatcher()
|
||||
|
||||
companion object {
|
||||
private var privateTestDispatcher: TestDispatcher? = null
|
||||
val testDispatcher: TestDispatcher
|
||||
get() = privateTestDispatcher
|
||||
?: throw IllegalStateException("StandardTestMainDispatcher is in afterEach State")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue