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 afterSpecHinaka
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