Hi in order to simplify unit testing I am trying to get changable context of the Corountene context like below (I am able to change context from test method)
object MyGlobalContext : CoroutineScope {
var context : CoroutineContext = Dispatchers.Default
override val coroutineContext: CoroutineContext
get() = context
}
Hovewer I am wondering weather it is possible to make averything created on the MyGlobalContext (like MyGlobalContext.launch; MyGlobalContext.actor etc) to run as runBlocking in order to make the methods synchronous for test purposes?
I'm not sure I understand what are you trying to achieve. Can you give a motivating example, like a test-case of how it is supposed to work?
ok so let's do some example
like previousle explained I will use my context object
object MyGlobalContext : CoroutineScope { var context : CoroutineContext = Dispatchers.Default override val coroutineContext: CoroutineContext get() = context }
1) my aim would be to test the function addOne
class testClass () {
var number = 1
fun addOne() {
MyGlobalContext.launch{ number ++}
}
}
the test function below will obviously fail becouse the addOne function will work on corountine
@ test fun test1(){
val cl =testClass ()
cl.addOne()
assertEquals(cl.number,2)
}
I could solve it by using delay like this
@ test fun test2(){
val cl =testClass ()
cl.addOne()
delay(100)
assertEquals(cl.number,2)
}
Hovewer such implementation of delay will obviously slow everything down and in some functions would be hard to determine how long it would take ( the function is using corountines internally it does not gives job or anything like this as its result)
So theoretically the best way would be to change the behaviour of MyGlobalContext so everything invoked by this object will work synchronously like in the modification below
fun addOne() {
runBlocking{ number ++}
in such case test1 function will work, and i tried to acheve this through changing context of MyGlobalContext but i failed so i will like to discover sth that would enable this (so i will use sth like that at the beginig of the tests). I imagine that it would look sth like this
MyGlobalContext.context = Dispatchers.blocking
@jakubMitura14 Can I ask you to format the code blocks in your examples according to the Kotlin code style, please. They are extremely hard to understand.
This is exactly why the concept of a "global scope" is discouraged. If you change your TestClass to inject the dispatcher or context to use via the constructor, like so:
class TestClass(private val context: CoroutineContext) {
private val scope = CoroutineScope(context)
var number = 1
fun addOne() {
scope.launch { number++ }
}
}
Then your test can simply inject Dispatchers.Unconfined and you'll have the behavior you're looking for:
@Test
fun test1() {
val cl = TestClass(Dispatchers.Unconfined)
cl.addOne()
assertEquals(cl.number, 2)
}
thank you for reply but I tested Dispatcher unconfied and it is not stopping the execution of code test what I show below and such test still fails
test("corountine scope dispatcher unconfied"){
var context : CoroutineContext = Dispatchers.Unconfined
val scope = CoroutineScope(context)
var x = 0
scope.launch {delay(100); x++ }
assertEquals(x,1)
}
I need to modify the test to sth like this in order for it to pass wchich do not solve my problem
test("corountine scope dispatcher unconfied"){
var x =0
runBlocking {delay(100); x++ }
assertEquals(x,1)
}
Here are 2 examples below that show you what you can do, hopefully that helps.
The issue you are seeing is that your assertEquals is not going to wait for the coroutine to have completed. Also you can problems for yourself modifying state outside a coroutine unless you are using the correct patterns to do so.
@Test
fun `corountine scope dispatcher unconfined 1`() {
var context: CoroutineContext = Dispatchers.Unconfined
val scope = CoroutineScope(context)
var x = 0
var job = scope.launch {
delay(100);
x++
}
runBlocking {
withTimeout(200) {
job.join()
assertThat(x).isEqualTo(1)
}
}
}
@Test
fun `corountine scope dispatcher unconfined 2`(){
var context : CoroutineContext = Dispatchers.Unconfined
val scope = CoroutineScope(context)
val x = 0
runBlocking {
var result = scope.async {
delay(100)
x+1 }
withTimeout(200) {
val returned = result.await()
assertThat(returned).isEqualTo(1)
}
}
}