Marco
Marco

Reputation: 350

Testing coroutines in the presenter class

I'm struggling to test my presenter which is calling a suspended function from the repository layer as follow:

 override fun viewCreated() {
        launch {
            val hasPermission = permissionChecker.execute() //suspended function
            if (hasPermission) {
                foo()
            } else {
                view.bar()
            }
         }

The presenter is also extending this interface:

interface CoroutinePresenter: CoroutineScope {

val job: Job

override val coroutineContext: CoroutineContext
    get() = Dispatchers.Main + job

fun stopAllActiveJobs() {
    coroutineContext.cancelChildren()
}

And the suspended function is defined as follow:

 suspend fun execute() : Boolean = withContext(Dispatchers.IO) {
    return@withContext class.foo()
}

Everything is working as expected in the app but when I tried to write some unit test I noticed that whenever I call the piece of code inside launch the thread is switched but the test doesn't wait for the execution. This is the implementation of the test:

@Test
fun `Test of Suspended Function`() = runBlocking {
    presenter.viewCreated()
    then(view).should().bar()
    ...
}

I also added the suggested library for testing kotlinx-coroutines-test but the result is still the same with it. I also tried to follow this suggestion and also implementing something like this but still no luck. I think the problem is the actual creation of another thread whenever the launch is invoked in the presenter and the test doesn't actually know how to wait for it. I also tried to return a Job and invoking the job.join() but it fails with a NullPointerException.

Hope you guys can help me.

Upvotes: 8

Views: 1202

Answers (2)

ibrahimyilmaz
ibrahimyilmaz

Reputation: 18919

Firstly, I strongly recommend that give your coroutineContext as a Parameter like that:

  class CoroutinePresenter(coroutineContext: CoroutineContext): CoroutineScope {
         init{
               _coroutineContext = coroutineContext
          }

         override val coroutineContext: CoroutineContext
                get() = _coroutineContext

         // Your Methods

    }

In your real environment:

 @YourScope
    @Provides
    fun providesCoroutinePresenter(coroutineContext:CoroutineContext ){
        return CoroutinePresenter()
    }
    @YourScope
    @Provides
    fun providesCoroutineContext(){
        return  Dispatchers.Main + job
    }

During the unit test:

    @Before
    fun setUp() {
       coroutinePresenter  CoroutinePresenter(Dispatchers.Unconfined)
    }

    @Test
    fun `Should do something`(){
         //WHEN
         coroutinePresenter.doSomething(params)
         //THEN
         do your assertions
    }

For more please check SOLID Principles and for this case D

Upvotes: 2

Marco
Marco

Reputation: 350

I found a solution for that: following this tutorial, I've setup both

@Before
fun setup() {
    Dispatchers.setMain(Dispatchers.Unconfined)
    ...
}
@After
fun tearDown() {
    Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
}

And by running the entire launch block of the presenter class inside a runBlocking statement in the test. The problem was related also to a not reported exception inside the suspended function that was actually not mocked but it was invisible to my eyes.

Now everything is working fine.

Upvotes: 2

Related Questions