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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With