Testing coroutines in the presenter class - android

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

Related

How to test suspend function using MockK?

I am writing a unit test for my Datarepository layer which simply calls an interface.
I am using Kotlin, coroutines and MockK for unit testing.
In MockK, how can I verify that I have called apiServiceInterface.getDataFromApi() and has happened only once?
Should I put the code in runBlocking?
This is my code:
UnitTest
import com.example.breakingbad.api.ApiServiceInterface
import com.example.breakingbad.data.DataRepository
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.verify
import org.junit.Test
Repository
class DataRepositoryTest {
#MockK
private lateinit var apiServiceInterface: ApiServiceInterface
#InjectMockKs
private lateinit var dataRepository: DataRepository
#Test
fun getCharacters() {
val respose = dataRepository.getCharacters()
verify { apiServiceInterface.getDataFromApi() }
}
}
class DataRepository #Inject constructor(
private val apiServiceInterface: ApiServiceInterface
) {
suspend fun getCharacters(): Result<ArrayList<Character>> = kotlin.runCatching{
apiServiceInterface.getDataFromApi()
}
}
Interface
interface ApiServiceInterface {
#GET("api/characters")
suspend fun getDataFromApi(): ArrayList<Character>
}
I think you should prefer using runTest instead of runBlocking or runBlockingTest.To tell you in brief about the three.
runBlocking allows you to call suspend functions by blocking a new coroutine and it blocks the current thread until it is completed.
runBlockingTest will immediately execute the suspending function skipping past any delay and enter coroutine block immediately unlike runBlocking which will wait for the amount of the delay
Since kotlinx.coroutines 1.6.0 release, runBlockingTest is deprecated in favour of runTest due to these reasons listed in the migration guide.
runTest() will automatically skip calls to delay() and handle uncaught exceptions. Unlike runBlockingTest() , it will wait for asynchronous callbacks to handle situations where some code runs in dispatchers that are not integrated with the test module.
I hope that answers your question of what to choose among these 3 to test your suspending function. You code would look like this -:
#Test
fun getCharacters() = runTest {
val response = dataRepository.getCharacters()
coVerify { apiServiceInterface.getDataFromApi() }
}
Also note that as David mentioned above, because getDataFromApi() is asynchronous/suspending function as well, you will have to use coVerify instead of verify to mock the same.
Yes, you should put the dataRepository.getCharacters() call in a runBlocking.
And the verify should be replaced for coVerify.
In the end, the test should look like this:
#Test
fun getCharacters() {
val respose = runBlocking { dataRepository.getCharacters() }
coVerify { apiServiceInterface.getDataFromApi() }
}
Also, since you want to verify it has happened only once, you need to call coVerify with the exactly parameter coVerify(exactly = 1)

What is the lifetime of coroutineScope in Kotlin?

The Code A is from the project architecture samples at https://github.com/android/architecture-samples
1: I don't know if the function activateTask(task: Task) need to be wrapped with runBlocking just like Code B. I'm afraid that activateTask(task: Task) maybe not be run if the object of DefaultTasksRepository is destroyed quickly.
2: Normally I run coroutines in ViewModel.viewModelScope, I don't know whether the ViewModel.viewModelScope will be destroyed when I finish the app, and whether the coroutines running in ViewModel.viewModelScope will be destroyed too. If so, I think it will be bad, some long time coroutines such as writing data to remote server will be cancel.
3: And more, the function activateTask in Code A is a coroutines function, it can invoke another coroutines function directly, so I think the Code A+ is correct, right?
Code A
import kotlinx.coroutines.coroutineScope
...
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
...
override suspend fun activateTask(task: Task) = withContext<Unit>(ioDispatcher) {
coroutineScope {
launch { tasksRemoteDataSource.activateTask(task) }
launch { tasksLocalDataSource.activateTask(task) }
}
}
override suspend fun clearCompletedTasks() {
coroutineScope {
launch { tasksRemoteDataSource.clearCompletedTasks() }
launch { tasksLocalDataSource.clearCompletedTasks() }
}
}
...
}
Code A+
import kotlinx.coroutines.coroutineScope
...
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
...
override suspend fun activateTask(task: Task) = withContext<Unit>(ioDispatcher) {
tasksRemoteDataSource.activateTask(task)
tasksLocalDataSource.activateTask(task)
}
override suspend fun clearCompletedTasks() {
tasksRemoteDataSource.clearCompletedTasks()
tasksLocalDataSource.clearCompletedTasks()
}
...
}
Code B
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello,")
}
You should not use runBlocking in any coroutine application, it blocks the thread.
If you really want to make activateTask non-cancellable there is a factory implementation of NonCancellable already in the stdlib
And you should not use coroutineScope wrapper inside the withContext, as a newly created CoroutineScope along with a new job is already passed as receiver within withContext.
Implement your activateTask like this:
override suspend fun activateTask(task: Task) = withContext<Unit>(ioDispatcher + NonCancellable) {
launch { tasksRemoteDataSource.activateTask(task) }
launch { tasksLocalDataSource.activateTask(task) }
}
In this way it will be called on the IODispatcher but will not be cancellable since the Job element of the resulting context does not provide functionality to cancel it.
ViewModelScope runs till your application is destroyed, more info and lifecycle chart is here. If you want to run some very important tasks, then use other dispatchers.
Yes code A+ is completely correct
PS: You should not implement runBlocking in a coroutine application, its default implementation is just the event loop.
runBlocking is the way to bridge synchronous and asynchronous code
Better implementation of main function should be:
suspend fun main() = coroutineScope {
// code here
}
It runs on the CommonPool, and if it suspends another coroutine could reuse the same thread.

Kotlin Coroutine Testing with Dispatchers.IO

So maybe there has been a tutorial going over this, but none of the ones I have read have addressed this issue for me. I have the structure as below and am trying to unit test, but when I go to test I always fails stating the repo method doSomthing() was never called. My best guess is because i have launched a new coroutine in a different context. How do I test this then?
Repository
interface Repository {
suspend fun doSomething(): String
}
View Model
class ViewModel(val repo: Repository) {
val liveData = MutableLiveData<String>()
fun doSomething {
//Do something here
viewModelScope.launch(Dispatchers.IO) {
val data = repo.doSomething()
withContext(Dispatchers.Main) {
liveData.value = data
}
}
}
}
View Model Test
class ViewModelTest {
lateinit var viewModel: ViewModel
lateinit var repo: Repository
#Before
fun setup() {
Dispatchers.setMain(TestCoroutineDispatcher())
repo = mock<Repository>()
viewModel = ViewModel(repo)
}
#Test
fun doSomething() = runBlockingTest {
viewModel.doSomething()
viewModel.liveData.test().awaitValue().assertValue {
// assert something
}
verify(repo).doSomthing()
}
}
According to Google:
Dispatchers should be injected into your ViewModels so you can properly test. You are setting the TestCorotutineDispatcher as the main Dispatcher via Dispatchers.setMain which takes control over the MainDispatcher, but you still have no control over the the execution of the coroutine launched via viewModelScope.launch(Dispatchers.IO).
Passing the Dispatcher via the constructor would make sure that your test and production code use the same dispatcher.
Typically an #Rule is defined that:
Overrides the MainDispatcher via Dispatchers.setMain (like you are doing)
Uses the TestCoroutineDispatcher's own runBlockingTest() to actually run the test.
Here is a really nice talk about testing and coroutines that happened at last year's Android Dev Summit.
And here is an example of such an #Rule. (Shameless plug. There are also examples of coroutine tests on that repo as well)
I write this solution for who use Dagger.
Inject CoroutineDispatcher in ViewModel constructor like this:
class LoginViewModel #Inject constructor(val dispatcher: CoroutineDispatcher) : BaseViewModel() {
and Provide Dispatcher like this:
#Singleton
#Provides
fun provideDispatchers(): CoroutineDispatcher = Dispatchers.IO
and in test package, Provide Dispatcher like this:
#Singleton
#Provides
fun provideDispatchers(): CoroutineDispatcher = UnconfinedTestDispatcher()
and now all lines in viewModelScope.launch(dispatcher) will be run

Mock unit testing for RxJava

I'm writing unit tests base on Google's samples: TaskDetailPresenterTest.kt#L102
They use ArgumentCaptor<TasksDataSource.GetTaskCallback> to trigger callback with fake data COMPLETED_TASK
#Test
fun getCompletedTaskFromRepositoryAndLoadIntoView() {
presenter = TaskDetailPresenter(COMPLETED_TASK.id, tasksRepository, taskDetailView)
presenter.start()
// Then task is loaded from model, callback is captured
verify(tasksRepository).getTask(
eq(COMPLETED_TASK.id), capture(taskCallbackCaptor))
// When task is finally loaded
taskCallbackCaptor.value.onTaskLoaded(COMPLETED_TASK) // Trigger callback
}
Everything work fine because they use TasksDataSource.GetTaskCallback to return data. See: TaskDetailPresenter.kt#L36:
fun getTask(taskId: String, callback: GetTaskCallback)
Then use as
tasksRepository.getTask(taskId, object : TasksDataSource.GetTaskCallback {
override fun onTaskLoaded(task: Task) {
showTask(task)
}
}
But when I try to use RxJava Single<> instead of normal callback, like:
fun getTask(taskId: String): Single<Task>
Then use as
tasksRepository.getTask(taskId)
.subscribe(object : SingleObserver<Task> {
override fun onSuccess(task: Task) {
showTask(task)
}
override fun onError(e: Throwable) {
}
})
}
Then I cannot use ArgumentCaptor<> to trigger return fake data. It always throw NullPointerException when I execute my test, because tasksRepository.getTask(taskId) is always return null.
So how can I achieve the same unit test like Google did, but in RxJava?
My unit test code:
#Mock private lateinit var tasksRepository: TasksRepository
#Captor private lateinit var taskCaptor: ArgumentCaptor<SingleObserver<Task>>
private lateinit var presenter: TaskDetailPresenter
#Before fun setup() {
MockitoAnnotations.initMocks(this)
}
#Test
fun getCompletedTaskFromRepositoryAndLoadIntoView() {
presenter = TaskDetailPresenter(COMPLETED_TASK.id, tasksRepository, taskDetailView)
presenter.start()
// Then task is loaded from model, callback is captured
verify(tasksRepository).getTask(
eq(COMPLETED_TASK.id)).subscribe(taskCaptor.capture())
// When task is finally loaded
taskCaptor.value.onSuccess(COMPLETED_TASK) // Trigger callback
}
Note that all other parts (declare, setup, mocking,..) is the same as Google.
I don't know if you have already used this library but I would suggest you to use the Dagger 2 library with a MVP code architecture to ease your unit tests by improving your dependencies and couplings
All this method is doing is showTask(task: Task). So assert that this method is called after your observer starts observing. You shouldn't care what the showTask is going to do once it's called. If you use Rx it is much better to make your methods take arguments and return value of the observe pattern to make it easier write unit test.

Best approach for unit-testing scoped viewmodels

When dealing with coroutines inside a viewModel is best to have said viewModel implement CoroutineScope so all coroutines are cancelled when the viewModel is cleared. Usually I see coroutineContext defined as Dispatchers.Main + _job so that coroutines are executed in the main UI thread by default. Usually this is done on a open class so that all your viewModels can extend it and get the scope without boilerplate code.
The issue arises when trying to unit test said viewModels as Dispatchers.Main is not available and trying to use it throws an exception. I am tryin to find a good solution that doesn't involve external libraries or too much boiler plate on the child viewModels.
My current solution is to add the maincontext as a contructor paramenter with the Dispatchers.Main as the default value. Then in the unit test, before testing the viewModel I set it to Dispatchers.Default. I don't quiet like this solution as it exposes the coroutineContext implementation details for everyone to see and change:
open class ScopedViewModel(var maincontext = Dispatchers.Main) : ViewModel(), CoroutineScope {
private val _job = Job()
override val coroutineContext: CoroutineContext
get() = maincontext + _job
override fun onCleared() {
super.onCleared()
_job.cancel()
}
}
class MyViewModel : ScopedViewModel() {}
In the tests:
fun setup(){
viewModel = MyViewModel()
viewModel.maincontext = Dispacther.Default
}
Personally I copied a solution from RxJava2: if your test runs against RxJava2 flow which includes two or more different schedulers, you want, sure, all of them to actually run in a single thread.
Here is how it is done with RxJava2 testing:
#BeforeClass
public static void prepare() {
RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setSingleSchedulerHandler(scheduler -> Schedulers.trampoline());
RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
}
I did the same for coroutines. Just have created a class which collects dispatchers, but these dispatchers can be changed.
object ConfigurableDispatchers {
#JvmStatic
#Volatile
var Default: CoroutineDispatcher = Dispatchers.Default
#JvmStatic
#Volatile
var Main: MainCoroutineDispatcher = Dispatchers.Main
...
}
And, inside #BeforeClass method I call
#ExperimentalCoroutinesApi
fun setInstantMainDispatcher() {
Main = object : MainCoroutineDispatcher() {
#ExperimentalCoroutinesApi
override val immediate: MainCoroutineDispatcher
get() = this
override fun dispatch(context: CoroutineContext, block: Runnable) {
block.run()
}
}
}
That will guarantee that the block will be executed in the calling thread.
It is the only alternative I found to constructor injection.

Categories

Resources