Kotlinx.coroutines: [question] Is this "IllegalStateException: This job has not completed yet" while using runBlockingTest normal?

Created on 25 May 2019  路  22Comments  路  Source: Kotlin/kotlinx.coroutines

Why when executing the following test:

@Test
fun foo() = runBlockingTest {
  bar()
}

suspend fun bar() {
  println(1)
  withContext(Dispatchers.IO) {
     println(2)
  }
  println(3)
}

The output is:

2
3

java.lang.IllegalStateException: This job has not completed yet

    at kotlinx.coroutines.JobSupport.getCompletionExceptionOrNull(JobSupport.kt:1114)
    at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:53)
    at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest$default(TestBuilders.kt:45)
    at com.foopkg.MyTests.foo(MyTests.kt:11)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:628)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:117)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:184)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:180)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:127)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1251)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1251)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Is it related to #1204 ?

test

Most helpful comment

I replaced runBlockingTest on runBlocking and it helped.

All 22 comments

I'm having the same problem. I'll paste the Test I'm running here, for if it helps

Test (NewsPresenterTest.kt)

@Test
fun `should load first page of news on start`() = runBlockingTest {
    givenThereAreNoNews()

    presenter.initialize()

    verify(mockNewsRepository).getNews(eq(1), anyLong())
}

Production code (NewsPresenter.kt)

override suspend fun initialize() {
    val result = coroutine { newsRepository.getNews(page) }

    // do more stuff with the result...
}

suspend fun <T> coroutine(
    context: CoroutineContext = Dispatchers.IO,
    block: suspend CoroutineScope.() -> T
): T = withContext(context, block)

Expected: Green, run successfully
Output: java.lang.IllegalStateException: This job has not completed yet

[Update] I've noticed that replacing Dispatchers.IO with Dispatchers.Main makes the Test go green. It only fails when using Dispatchers.IO Context

I'm sorry I can't post it to a public GitHub repository :-( I can post any additional code that might be necessary for diagnosing this

cc @objcode

Thanks for the minimal repro!

In the current version there's an issue that causes runBlockingTest to throw this exception incorrectly when there are multiple threads. This is the same issue reported in https://github.com/Kotlin/kotlinx.coroutines/issues/1204

Patch https://github.com/Kotlin/kotlinx.coroutines/pull/1206 will change this to pass as expected.

Been running this with ktor-client tests as well - is the best workaround for now to just use runBlocking (if you don't entirely need all the APIs and support from runBlockingTest)?

I replaced runBlockingTest on runBlocking and it helped.

I replaced runBlockingTest on runBlocking and it helped.

runBlocking works for me too

private val testDispatcher = TestCoroutineDispatcher()

@Before
fun setup() {
    Dispatchers.setMain(testDispatcher)
}

@After
fun cleanUp() {
    Dispatchers.resetMain()
    testDispatcher.cleanupTestCoroutines()
}

@Test
fun testCoroutines1() = runBlocking {
    withContext(Dispatchers.Main) {
        Assert.assertEquals(job1().await() and job2().await(), false)
    }
}

@Test
fun testCoroutines2() = runBlocking {
    withContext(Dispatchers.Main) {
        Assert.assertEquals(job1().await() or job2().await(), true)
    }
}

suspend fun job1() = GlobalScope.async(Dispatchers.Default) {
    delay(1000)
    true
}

suspend fun job2() = GlobalScope.async(Dispatchers.Default) {
    delay(500)
    false
}

The one annoying thing about runBlocking is that JUnit tests don't run unless they return Unit, but it's hard to remember to always type runBlocking<Unit> orfun testFoo(): Unit = runBlocking`.

We use this just to get around that:

fun testBlocking(test: suspend CoroutineScope.() -> Unit) = runBlocking { test() }

Hi,

i'm facing same issue as described above executing my room database unit tests.

Has there been found out any fix for that ?

Still continuing. I am using a custom CoroutinesTestRule but still getting this issue. Any workaround except using runBlocking?

@qwwdfsad Is there any place where we can track the progress?
I can see the https://github.com/Kotlin/kotlinx.coroutines/pull/1206 was closed, but I couldn't find any further updates.

@mateuszkwiecinski

FWIW I've been using https://github.com/kotest/kotest for new tests, which just supports coroutines without having to do anything like runBlockingTest at all, and it's been great overall.

Wrote this earlier, but ended up deleting it because I though I hadn't fixed the issue after all. Turned out it was unrelated.

Not sure if it's painfully obvious to everyone else, but I had a lot of issues with this, because I didn't understand the problem.

I use a TestCoroutineScope for my tests.
@get: Rule
var mainCoroutineRule = MainCoroutineRule()

Not sure about the details, but I think it replaces every Dispatcher.MAIN with a test dispatcher.
The "This job has not completed yet" pop up when you call code that doesn't run on Dispatchers.MAIN, like Dispatchers.IO

My repository is filled with code that runs on Dispatchers.IO. To fix this issue, the repository constructor now takes a Dispatcher as a parameter,
and to make it work work with the rest of my code without changes, it defaults to the Dispatchers.IO

Whenever I run tests now, I pass in the TestCoroutineScope.dispatcher to the repository, and everything works as expected.

Wrote this earlier, but ended up deleting it because I though I hadn't fixed the issue after all. Turned out it was unrelated.

Not sure if it's painfully obvious to everyone else, but I had a lot of issues with this, because I didn't understand the problem.

I use a TestCoroutineScope for my tests.
@get: Rule
var mainCoroutineRule = MainCoroutineRule()

Not sure about the details, but I think it replaces every Dispatcher.MAIN with a test dispatcher.
The "This job has not completed yet" pop up when you call code that doesn't run on Dispatchers.MAIN, like Dispatchers.IO

My repository is filled with code that runs on Dispatchers.IO. To fix this issue, the repository constructor now takes a Dispatcher as a parameter,
and to make it work work with the rest of my code without changes, it defaults to the Dispatchers.IO

Whenever I run tests now, I pass in the TestCoroutineScope.dispatcher to the repository, and everything works as expected.

Yes I confirm this behavior, I have started to inject Dispatcher in constructors and now all my tests pass

Using runBlocking instead of runBlockingTest worked for me as well.

In my case, I had to do this while using runBlockingTest:

val job = launch {
            val tasksLists = tasksLisDao.tasksLists()

            assertEquals(tasksLists.toList(), testValues)
        }

        job.cancel()

I am not sure if it is the right way but the test passed thou.

In my case, I had to do this while using runBlockingTest:

val job = launch {
            val tasksLists = tasksLisDao.tasksLists()

            assertEquals(tasksLists.toList(), testValues)
        }

        job.cancel()

I am not sure if it is the right way but the test passed thou.

using this if assert value is not equal then also the test will get pass

This code worked in my scenario where repo.task() is an API call with error "This job has not completed yet"

@Test
    fun getMethod() = runBlocking {
         val result = withTimeoutOrNull(1300L) {
           repo.task()
         }
         assertEquals(expected, actual)
        Unit
   }

make sure task() is suspendCancellableCoroutine

My problem was I was using Robolectric to test a class that used Google's LocationProvider for Location and using a suspend function locationManager.getLocation() which caused the exception with runBlockingTest .

The only solution was this to use GlobalScope.launch { } to test it. I didn't even need the coroutine test rules or anything:

class LocationManager(private val fusedLocationProviderClient: FusedLocationProviderClient) {

    @SuppressLint("MissingPermission")
    suspend fun getLocation(): LocationCoordinates? =
        fusedLocationProviderClient.lastLocation.await().toLocationCoordinates()

    data class LocationCoordinates(val long: Double, val lat: Double)

    private fun Location.toLocationCoordinates(): LocationCoordinates = LocationCoordinates(this.longitude, this.latitude)
}
@RunWith(AndroidJUnit4::class)
@Config(sdk = [Build.VERSION_CODES.P])
class LocationManagerTest {
    private lateinit var locationManager: LocationManager

    @Before
    fun setUp() {
        val client = LocationServices.getFusedLocationProviderClient(getApplicationContext() as Context)
        client.setMockMode(true)
        val location = Location("lol")
        location.longitude = 11.0
        location.latitude = 12.0
        client.setMockLocation(location)
        locationManager = LocationManager(client)
    }

    @Test
    fun `test location coordinates`() {
        GlobalScope.launch {
            val location = locationManager.getLocation()
            assertEquals(location!!.long, 11.0)
            assertEquals(location.lat, 12.0)
        }
    }
}

Initially I was running the test like this, with coroutine test dispatchers and instant task executor rules and whatnot, and got the Job not completed exception:

    @Test
    fun `test location coordinates`() = runBlockingTest {
            val location = locationManager.getLocation()
            assertEquals(location!!.long, 11.0)
            assertEquals(location.lat, 12.0)
    }

this also caused an exception:

    @Test
    fun `test location coordinates`() = testContextProvider.testCoroutineDispatcher.runBlockingTest {
          val location = locationManager.getLocation()
            assertEquals(location!!.long, 11.0)
            assertEquals(location.lat, 12.0)
    }

Maybe this will help someone.

Want to test this simple funtion

suspend fun resetPassword(email: String) {
        val forgotPasswordData = ForgotPasswordData(email)
        restClient.user(sharedPreferencesStore.sessionTokenId).forgotPassword(forgotPasswordData)
    }

restClient.user() returns MariaUserRoutes

fun user(sessionToken: String): MariaUserRoutes {
        return get(parameters = { parametersOf(applicationId, baseUrl, sessionToken) })
}

MariaUserRoutes.forgotPassword doesn't return anything

suspend fun forgotPassword(data: ForgotPasswordData) = RestRequest<Unit>(
        RestRequestComponents(
            "$baseUrl/user/forgotpassword",
            get<HeadersGenerator>().getHeaders(applicationId),
            data.toJson().toByteArray(),
            RequestMethod.POST
        )
    ).send()

This is my test:

@Test
 fun `Should login successfully`() {
        testCoroutineRule.runBlockingTest {
            val loginData = LoginData(email, password)
            val loginResult = LoginResult(user, session)
            val mariaUserRoutes = MariaUserRoutes("applicationId", "baseUrl", session.token)
            whenever(sharedPreferencesStore.sessionTokenId).thenReturn(session.token)

            whenever(restClient.user(sharedPreferencesStore.sessionTokenId)).thenReturn(mariaUserRoutes)
            whenever(mariaUserRoutes.login(loginData)).thenReturn(loginResult)

            loginManager.login(email, password)

            verify(restClient).user(any())
        }
    }

Using Robolectric
I was also trying to use coroutine flow, GlobalScope and just runBlocking - neither of these approaches worked
Probably I'm doing something wrong

you can add rule

@get: Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()

this will resolve the issue and make sure instantTaskExecutorRule is not private.

Sample code::

@RunWith(AndroidJUnit4::class)
@SmallTest
class GalleryDaoTest {

@get: Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()

private lateinit var database: GalleryDatabase
private lateinit var dao: GalleryDao

@Before
fun setUp() {
    database = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), GalleryDatabase::class.java).allowMainThreadQueries().build()
    dao = database.getGalleryDao()
}

@Test
fun insertGallery() = runBlockingTest {

    val galleryItem = GalleriesItem(1,1, "url", "title", "body")
    dao.insertGallery(galleryItem)
    val result = dao.getAllGalleries().getOrAwaitValue()
    assertThat(result).contains(galleryItem)
}

}
}

Was this page helpful?
0 / 5 - 0 ratings