https://kotlinlang.org logo
#coroutines
Title
# coroutines
m

Marko Novakovic

06/04/2021, 6:06 PM
Copy code
This job has not completed yet
java.lang.IllegalStateException: This job has not completed yet
	at kotlinx.coroutines.JobSupport.getCompletionExceptionOrNull(JobSupport.kt:1190)
	at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:53)
	at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest$default(TestBuilders.kt:45)
am trying to run some tests and I get this error? what is the cause of it and how to fix it?
e

Erik

06/05/2021, 6:34 AM
You maybe are launching coroutines that are still running while your test block finishes. Those coroutines aren't completed yet, which is illegal, as your code is still running and potentially doing things that you haven't covered by the test.
m

Marko Novakovic

06/07/2021, 4:05 AM
No. Class am testing had only one function and am calling it once in tests.
runBlockingTest
gives above error but
runBlocking
does not
e

Erik

06/07/2021, 6:46 AM
runBlockingTest
is designed to fail when a (child) job isn't finished yet.
runBlocking
doesn't care. Usually you shouldn't use
runBlocking
in tests, because your tests might slow down a lot (delays will actually delay as long as they should) and leaking child coroutines won't be detected, leading to a fragile test system.
For example:
Copy code
@Test
fun test() = runBlocking {
    println("Before launch")
    launch(Dispatchers.Default /* Switch to non-blocking context */) {
        delay(100)
        println("Inside launch")
    }
    println("After launch")
}
This test will pass, but that's likely a bug! If you change it to
runBlockingTest
, you will be properly notified that a job was still active when the test block was finished, which is a leaking child coroutine. In fact, the
launch
call launches the child coroutine in a non-blocking context, so it can run concurrently with the blocking context. Also note the order of the
println
output, which shows that the launched child job is running concurrently and still executes after the last
println
statement (the bug)
The solution in this example would be to inject the dispatcher, and in a test use the test dispatcher instead of a production dispatcher
m

Marko Novakovic

06/07/2021, 10:19 AM
@Erik thank you very much. I’ve been able to solve the issue. I didn’t want to use
runBlocking
so I tried to fix the error and I succeeded
one more question
how to test
Flow
and
MutableStateFlow
?
MutableStateFlow
is not “regular”
Flow
and
MutableStateFlow
was causing issue in my tests
my implementation emits
Uninitialized
state first and than state in response to function call. do I use
drop(1)
or there is something else that I should use?
e

Erik

06/07/2021, 10:48 AM
I'm happy to answer your question, but next time ask a new one in the channel
m

Marko Novakovic

06/07/2021, 10:49 AM
I can ask this one in the channel
e

Erik

06/07/2021, 10:49 AM
I would test 2 things:
- Assert that the
first
item is your initial state
- Assert the rest by
drop(1)
, like you said
Seem like a fine approach 👍
m

Marko Novakovic

06/07/2021, 10:50 AM
thank you very much
e

Erik

06/07/2021, 10:50 AM
Good luck with your implementations! 🙂
m

Marko Novakovic

06/07/2021, 10:50 AM
does it make a difference if I test
MutableStateFlow
directly and when I expose
flow
backed by
MutableStateFlow
?
like this
Copy code
private val _state = MutableStateFlow(1)
val state: Flow<Int> = _state
e

Erik

06/07/2021, 10:51 AM
Test the interface of your class, so if it has a public
Flow
or
StateFlow
in the API, test the public version
m

Marko Novakovic

06/07/2021, 10:52 AM
I understand. what I was asking is does it make a difference in tests.
StateFlow
is hot flow and
collect
doesn’t work as on standard
Flow
e

Erik

06/07/2021, 10:53 AM
Consider using (just a detail)
Copy code
private val _state = MutableStateFlow(1)
val state: Flow<Int> = _state.asStateFlow()
So that the consumer of
state
cannot cast
state as MutableStateFlow
(it could start emitting states if it could cast it!)
m

Marko Novakovic

06/07/2021, 10:54 AM
interesting. thanks
will do that
e

Erik

06/07/2021, 10:54 AM
The
Flow
interface specifies very generic
collect
behaviour. The
StateFlow
has more specific
collect
behaviour
Depending on what behaviour you want to expose, you expose a flow or state flow
m

Marko Novakovic

06/07/2021, 10:55 AM
that’s what I was asking. thank you for your time, this was really helpful
e

Erik

06/07/2021, 10:55 AM
E.g. a flow doesn't promise that there is a value, but a state flow does promise that there always is one and that it is always repeated
👍 1
You're welcome
Feel free to ask anything on the #coroutines channel, there's enough helpful folk around
m

Marko Novakovic

06/07/2021, 10:56 AM
I will, and I will post new questions in new messages 😄
e

Erik

06/07/2021, 10:56 AM
Depending on how complex you want your tests to become, try this library to test flows: https://github.com/cashapp/turbine
You might not need it, but take a look at what it can do, maybe you discover that it helps you test your flows!
m

Marko Novakovic

06/07/2021, 10:59 AM
am taking a look now
164 Views