1

I've looked at this SO post and made sure my tests don't share the same mocked instances, but the tests still fail when run together but succeed when run individually.

I suspect there might be something that prevents my instances from being re-mocked after every test, but I'm not experienced enough with Mockito to identify the issue

Here is my test class

package com.relic.viewmodel

import android.arch.core.executor.testing.InstantTaskExecutorRule
import android.arch.lifecycle.Observer
import com.nhaarman.mockitokotlin2.*
import com.relic.api.response.Data
import com.relic.api.response.Listing
import com.relic.data.PostRepository
import com.relic.data.UserRepository
import com.relic.data.gateway.PostGateway
import com.relic.domain.models.ListingItem
import com.relic.domain.models.UserModel
import com.relic.presentation.displayuser.DisplayUserVM
import com.relic.presentation.displayuser.ErrorData
import com.relic.presentation.displayuser.UserTab
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.setMain
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@ExperimentalCoroutinesApi
class UserVMTest {
    @get:Rule
    val rule = InstantTaskExecutorRule()

    private lateinit var postRepo : PostRepository
    private lateinit var userRepo : UserRepository
    private lateinit var postGateway : PostGateway

    private val username = "testUsername"

    init {
        val mainThreadSurrogate = newSingleThreadContext("Test thread")
        Dispatchers.setMain(mainThreadSurrogate)
    }

    @Before
    fun setup() {
        postRepo = mock()
        userRepo = mock()
        postGateway = mock()
    }

    @Test
    fun `user retrieved on init`() = runBlocking {
        val mockUser = mock<UserModel>()
        whenever(userRepo.retrieveUser(username)).doReturn(mockUser)

        val vm = DisplayUserVM(postRepo, userRepo, postGateway, username)

        val observer : Observer<UserModel> = mock()
        vm.userLiveData.observeForever(observer)

        verify(userRepo, times(1)).retrieveUser(username)
        verify(observer).onChanged(mockUser)
    }

    @Test
    fun `livedata updated when posts retrieved` () = runBlocking {
        val mockListingItems = listOf<ListingItem>(mock())
        val listing = mockListing(mockListingItems)
        whenever(postRepo.retrieveUserListing(any(), any(), any())).doReturn(listing)

        val tab = UserTab.Saved
        val vm = DisplayUserVM(postRepo, userRepo, postGateway, username)

        val observer : Observer<List<ListingItem>> = mock()
        vm.getTabPostsLiveData(tab).observeForever(observer)
        vm.requestPosts(tab, true)

        verify(postRepo, times(1)).retrieveUserListing(any(), any(), any())
        verify(observer, times(1)).onChanged(mockListingItems)
    }

    @Test
    fun `error livedata updated when no posts retrieved` () = runBlocking {
        val listing = mockListing()
        val localPostRepo = postRepo
        whenever(localPostRepo.retrieveUserListing(any(), any(), any())).doReturn(listing)

        val tab = UserTab.Saved
        val vm = DisplayUserVM(postRepo, userRepo, postGateway, username)

        val listingObserver : Observer<List<ListingItem>> = mock()
        vm.getTabPostsLiveData(tab).observeForever(listingObserver)

        val errorObserver : Observer<ErrorData> = mock()
        vm.errorLiveData.observeForever(errorObserver)

        vm.requestPosts(tab, true)

        verify(postRepo, times(1)).retrieveUserListing(any(), any(), any())
        // listing livedata shouldn't be updated, but an "error" should be posted
        verify(listingObserver, never()).onChanged(any())
        verify(errorObserver, times(1)).onChanged(ErrorData.NoMorePosts(tab))
    }

    private fun mockListing(
        listingItems : List<ListingItem> = emptyList()
    ) : Listing<ListingItem> {

        val data = Data<ListingItem>().apply {
            children = listingItems
        }

        return Listing(kind = "", data = data)
    }
}
boiledbuns
  • 95
  • 1
  • 1
  • 11
  • I have 0 to none experience with co-routines. All I know is what I read, so maybe this question makes no sense, but I thought I'd asked. Aren't we suppose to add `launch(Dispatchers.Main) { /*...*/ }` inside the `runBlocking` to effectively put the code running in the unit tests' main thread? Or instead of `runBlocking` shouldn't it be `runBlockingTest`? – Fred Jun 16 '19 at 08:23
  • Thanks for the reply, it turns out I was running the test with the wrong dispatcher. I'll post an update the solution below – boiledbuns Jun 16 '19 at 17:07

1 Answers1

2

Ok so after a bit more reading, it I've updated my code so that it uses the TestCoroutineDispatcher instead of newSingleThreadContext(). I also added a teardown method per the instructions here https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/

@After
fun teardown() {
    Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
    mainThreadSurrogate.cleanupTestCoroutines()
}

Tests run successfully now

boiledbuns
  • 95
  • 1
  • 1
  • 11