Hinaka
10/21/2021, 3:47 PMclass TestClass {
private val testDispatcher = TestCoroutineDispatcher()
@Before
fun setup() {
// provide the scope explicitly, in this example using a constructor parameter
Dispatchers.setMain(testDispatcher)
}
@After
fun cleanUp() {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
@Test
fun testFoo() = testDispatcher.runBlockingTest {
// TestCoroutineDispatcher.runBlockingTest uses `testDispatcher` to run coroutines
foo()
}
}
fun foo() {
MainScope().launch {
// launch will use the testDispatcher provided by setMain
}
}
and inject that testDispatcher
to any class that need a dispatcher. Can I do the same using kotest, especially with the BehaviorSpec
?sam
10/21/2021, 3:52 PM@Before
with beforeSpec
and @After
with afterSpec
Hinaka
10/21/2021, 4:02 PM@Test
fun testFoo() = testDispatcher.runBlockingTest {
// TestCoroutineDispatcher.runBlockingTest uses `testDispatcher` to run coroutines
foo()
}
by starting the test with testDispatcher.runBlockingTest
I making sure that all code will be connect to the same dispatcher, and I can easily manipulate time to test. Normally this is what I do:
@Test
fun testFoo() = testDispatcher.runBlockingTest {
// Given
// When
// Then
}
Can I config kotest to do the same thing with BehaviorSpec
, wrap a runBlockingTest
around Given
, When
, and Then
?sam
10/21/2021, 4:14 PMsam
10/21/2021, 4:14 PMLeoColman
10/21/2021, 5:28 PMLeoColman
10/21/2021, 5:28 PMLeoColman
10/21/2021, 5:28 PMHinaka
10/22/2021, 4:33 AMtestDispatcher.runBlockingTest
and inject that same dispatcher to be used by withContext
in code will make sure that every thing will link to the same TestCoroutineScope
, and we can easily use pauseDispatcher
, advanceTimeBy
... to manipulate time for testing purpose. Is it possible to do the same in kotest?sam
10/22/2021, 4:36 AMHinaka
10/22/2021, 4:47 AMTestListener
but do not find any way to do what I want, replace spec dispatcher with my own testDispatcher
.sam
10/22/2021, 4:49 AMclass TestClass : FunSpec() {
private val testDispatcher = TestCoroutineDispatcher()
init {
beforeSpec {
// provide the scope explicitly, in this example using a constructor parameter
Dispatchers.setMain(testDispatcher)
}
afterSpec {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
test("testFoo") {
testDispatcher.runBlockingTest {
// TestCoroutineDispatcher.runBlockingTest uses `testDispatcher` to run coroutines
foo()
}
}
}
Hinaka
10/22/2021, 5:27 AMbeforeSpec
can replace the common used MainCoroutineRule
.
test("testFoo") {
testDispatcher.runBlockingTest {
// TestCoroutineDispatcher.runBlockingTest uses `testDispatcher` to run coroutines
foo()
}
use runBlockingTest
like this, will it provide the same scope to nested test?Hinaka
10/22/2021, 5:30 AM@ExperimentalCoroutinesApi
class Test {
private val testDispatcher = TestCoroutineDispatcher()
@Test
fun testFooWithLaunchAndDelay() = runBlockingTest {
pauseDispatcher()
foo()
println(2)
}
suspend fun CoroutineScope.foo() {
launch(testDispatcher) {
println(1)
delay(1_000)
println(3)
}
}
}
Since the pauseDispatcher
use with a different code from testDispatcher
, it won't run correctly, in fact, this will throw an exception.sam
10/22/2021, 5:32 AMHinaka
10/22/2021, 5:32 AM@ExperimentalCoroutinesApi
class Test {
private val testDispatcher = TestCoroutineDispatcher()
@Test
fun testFooWithLaunchAndDelay() = testDispatcher.runBlockingTest {
pauseDispatcher()
foo()
println(2)
}
suspend fun CoroutineScope.foo() {
launch(testDispatcher) {
println(1)
delay(1_000)
println(3)
}
}
}
replace runBlockingTest
with testDispatcher.runBlockingTest
, making sure that all code link to the same TestCoroutineScope
, now it can run correctly 2, 1, 3
.Hinaka
10/22/2021, 5:33 AMrunCurrent
or advanceTimeBy
, or advanceUntilIdle
to execute coroutines."sam
10/22/2021, 5:33 AMsam
10/22/2021, 5:34 AM@Before
with beforeSpec and so onsam
10/22/2021, 5:34 AMHinaka
10/22/2021, 5:36 AMtestDispatcher
usage to run the test, but that code also contains the logic to replace Dispatchers.Main
.
What you suggest is great, it's look like the commonly used MainCoroutineRule
in junit.sam
10/22/2021, 5:38 AMsam
10/22/2021, 5:38 AMHinaka
10/22/2021, 5:38 AMSpec.dispatcherAffinity
said that "By default, all tests inside a single spec are executed using the same dispatcher to ensure that callbacks all operate on the same thread". Is there anyway I can replace this "same dispatcher" with my own dispatcher?sam
10/22/2021, 5:42 AMsam
10/22/2021, 5:43 AMobject : SpecExtension {
override suspend fun intercept(spec: Spec, process: suspend () -> Unit) {
withContext(yourdispatcher) { process(spec) }
}
}
sam
10/22/2021, 5:43 AMHinaka
10/22/2021, 6:22 AMsam
10/22/2021, 6:31 AMHinaka
10/22/2021, 7:41 AMTestCoroutineScope
as the root of the test, hence fun test() = testDispatcher.runBlockingTest{}
. With the SpecExtension
, correct me if I wrong, even if I write this testDispatcher.runBlockingTest{ process(spec) }
, that TestCoroutineScope
won't become a root scope, right?sam
10/22/2021, 7:42 AMsam
10/22/2021, 7:43 AMHinaka
10/22/2021, 8:20 AMwithContext(testDispatcher)
and testDispatcher.runBlockingTest
, but it not worked in every case, especially can't handle delay
. I'm not that knowledgeable about the internal working of coroutine so I can't say for sure, but I think this error maybe because TestCoroutineScope
not a root scope of test.Hinaka
10/22/2021, 8:29 AM