jeudi 6 août 2020

How do I test a Kotlin suspend call in Android with MockK?

I'm trying my hand at TDD with an Android app. I'm writing it in Kotlin, and because of that I've turned to MockK for testing, but there's one thing (for now) that I haven't been able to find out how to do: test a suspend call.

I wrote a test for a LiveData value in a ViewModel, and made it work. However, when I added coroutines to the mix, I started getting the "Method getMainLooper not mocked" message.

Here's my code:

ToDoListViewModelTest.kt

class ToDoListViewModelTest {

    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @MockK
    private lateinit var toDoListLiveDataObserver: Observer<List<ToDoItem>>

    @MockK
    private lateinit var getToDoItemsUseCase: GetToDoItemsUseCase

    @Before
    fun setUp() {
        MockKAnnotations.init(this)
        every { toDoListLiveDataObserver.onChanged(any()) } answers { nothing }
    }

    @Test
    fun toDoList_listItems_noItems() = runBlocking {

        coEvery { getToDoItemsUseCase() } coAnswers { emptyList<ToDoItem>() }

        val toDoListViewModel = ToDoListViewModel(getToDoItemsUseCase)
        toDoListViewModel.toDoItemList.observeForever(toDoListLiveDataObserver)

        toDoListViewModel.updateItemList()

        assertEquals(0, toDoListViewModel.toDoItemList.value?.size)
    }
}

ToDoListViewModel.kt

class ToDoListViewModel(private val getToDoItemsUseCase: GetToDoItemsUseCase) : ViewModel() {

    private val _toDoItemList: MutableLiveData<List<ToDoItem>> = MutableLiveData()
    val toDoItemList : LiveData<List<ToDoItem>> = _toDoItemList

    fun updateItemList() {
        viewModelScope.launch(Dispatchers.IO) {
            _toDoItemList.value = getToDoItemsUseCase()
        }
    }
}

GetToDoItemsUseCase.kt

class GetToDoItemsUseCase {
    suspend operator fun invoke(): List<ToDoItem> {
        return listOf()
    }
}

Things I've tried:

  • Adding "@RunWith(BlockJUnit4ClassRunner::class)": No change
  • Adding "testOptions { unitTests.returnDefaultValues = true }" to the Gradle file: The Looper error goes away, but the value coming from the LiveData is null, instead of the empty list specified in the "coEvery" call.
  • Calling "Dispatchers.setMain(newSingleThreadContext("UI Thread"))": Same as previous case, getting null from LiveData.

I'm not very experienced with testing, and I've run out of options. I feel I definitely need some help from the community ;)

Also, if for some reason my setup isn't the right one (should use something other than MockK, or some other testing framework...), please comment on that too. I still have much to learn regarding this.

Aucun commentaire:

Enregistrer un commentaire