Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing coroutines runBlockingTest: This job has not completed yet

Please find below a function using a coroutine to replace callback :

override suspend fun signUp(authentication: Authentication): AuthenticationError {     return suspendCancellableCoroutine {         auth.createUserWithEmailAndPassword(authentication.email, authentication.password)             .addOnCompleteListener(activityLifeCycleService.getActivity()) { task ->                 if (task.isSuccessful) {                     it.resume(AuthenticationError.SignUpSuccess)                 } else {                     Log.w(this.javaClass.name, "createUserWithEmail:failure", task.exception)                     it.resume(AuthenticationError.SignUpFail)                 }             }     } } 

Now I would like to unit testing this function. I am using Mockk :

  @Test   fun `signup() must be delegated to createUserWithEmailAndPassword()`() = runBlockingTest {        val listener = slot<OnCompleteListener<AuthResult>>()       val authentication = mockk<Authentication> {         every { email } returns "email"         every { password } returns "pswd"       }       val task = mockk<Task<AuthResult>> {         every { isSuccessful } returns true       }        every { auth.createUserWithEmailAndPassword("email", "pswd") } returns           mockk {             every { addOnCompleteListener(activity, capture(listener)) } returns mockk()           }      service.signUp(authentication)        listener.captured.onComplete(task)     } 

Unfortunately this test failed due to the following exception : java.lang.IllegalStateException: This job has not completed yet

I tried to replace runBlockingTest with runBlocking but the test seems to wait in an infinite loop.

Can someone help me with this UT please?

Thanks in advance

like image 846
suns9 Avatar asked Apr 15 '20 08:04

suns9


1 Answers

As can be seen in this post:

This exception usually means that some coroutines from your tests were scheduled outside the test scope (more specifically the test dispatcher).

Instead of performing this:

private val networkContext: CoroutineContext = TestCoroutineDispatcher()  private val sut = Foo(   networkContext,   someInteractor )  fun `some test`() = runBlockingTest() {   // given   ...    // when   sut.foo()    // then   ... } 

Create a test scope passing test dispatcher:

private val testDispatcher = TestCoroutineDispatcher() private val testScope = TestCoroutineScope(testDispatcher) private val networkContext: CoroutineContext = testDispatcher  private val sut = Foo(   networkContext,   someInteractor ) 

Then in test perform testScope.runBlockingTest

fun `some test`() = testScope.runBlockingTest {   ... }  

See also Craig Russell's "Unit Testing Coroutine Suspend Functions using TestCoroutineDispatcher"

like image 178
azizbekian Avatar answered Sep 19 '22 13:09

azizbekian