Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 604
Marco Avatar asked Jul 05 '19 14:07

Marco


2 Answers

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.

like image 112
Marco Avatar answered Sep 20 '22 03:09

Marco


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

like image 41
ibrahimyilmaz Avatar answered Sep 22 '22 03:09

ibrahimyilmaz