Issue
I am new to testing and I wanted to learn how to test Coroutines with MVVM pattern. I just followed https://github.com/android/architecture-samples project and did a few changes (removed remote source). But when testing the ViewModel for fetching data from a repository, it keeps on failing with this error.
value of : iterable.size()
expected : 3
but was : 0
iterable was: []
Expected :3
Actual :0
Below is my test class for the ViewModel don't know what I'm missing. Also when mocking the repository I can get the expected results from it when printing taskRepository.getTasks() it just doesn't reflect on the LiveData when calling loadTasks()
ViewModelTest
@ExperimentalCoroutinesApi
@RunWith(MockitoJUnitRunner::class)
class TasksViewModelTest {
private lateinit var tasksViewModel: TasksViewModel
val tasksRepository = mock(TasksRepository::class.java)
@ExperimentalCoroutinesApi
@get:Rule
var mainCoroutineRule = TestMainCoroutineRule()
// Executes each task synchronously using Architecture Components.
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(tasksRepository)
}
@Test
fun whenLoading_hasListOfTasks() = runBlockingTest {
val task1 = Task("title1", "description1")
val task2 = Task("title2", "description2")
val task3 = Task("title3", "description3")
`when`(tasksRepository.getTasks()).thenReturn(Result.Success(listOf(
task1,
task2,
task3
)))
tasksViewModel.loadTasks()
val tasks = LiveDataTestUtil.getValue(tasksViewModel.tasks)
assertThat(tasks).hasSize(3)
}
}
TasksViewModel
class TasksViewModel @Inject constructor(
private val repository: TasksRepository
) : ViewModel() {
private val _tasks = MutableLiveData<List<Task>>().apply { value = emptyList() }
val tasks: LiveData<List<Task>> = _tasks
fun loadTasks() {
viewModelScope.launch {
val tasksResult = repository.getTasks()
if (tasksResult is Success) {
val tasks = tasksResult.data
_tasks.value = ArrayList(tasks)
}
}
}
}
Helper classes are listed below, I just copied the same classes from the sample project.
LiveDataTestUtil
object LiveDataTestUtil {
/**
* Get the value from a LiveData object. We're waiting for LiveData to emit, for 2 seconds.
* Once we got a notification via onChanged, we stop observing.
*/
fun <T> getValue(liveData: LiveData<T>): T {
val data = arrayOfNulls<Any>(1)
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data[0] = o
latch.countDown()
liveData.removeObserver(this)
}
}
liveData.observeForever(observer)
latch.await(2, TimeUnit.SECONDS)
@Suppress("UNCHECKED_CAST")
return data[0] as T
}
}
MainCoroutineRule
@ExperimentalCoroutinesApi
class TestMainCoroutineRule : TestWatcher(), TestCoroutineScope by TestCoroutineScope() {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(this.coroutineContext[ContinuationInterceptor] as CoroutineDispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
}
}
Solution
Turns out it was a problem with mockito, I have an older version, and I found there's a library called mockito-kotlin to simplify testing coroutines as stated here. I then chaged my code to this and It's working well.
tasksRepository.stub {
onBlocking { getTasks() }.doReturn(Result.Success(listOf(task1, task2, task3)))
}
Answered By - Ralph
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.