Hot questions for Using Mockito in viewmodel

Question:

I am using AndroidViewModel with LiveData to send Intents to a IntentService and receiving events from an EventBus. I need the Application Context for the Intents and the EventBus.

What is the best way to test AndroidViewModel classes with local tests? I can get it to start with Robolectrics RuntimeEnvironment.application but there doesnt seem to be a shadowOf() for AndroidViewModel to check if the right Intents were sent to the correct receiver.

Perhaps it is somehow possible to do this with Mockito using my own mock-intents and inject them into my AndroidViewModel, but that doesn't seem to be very straightforward.

My code looks something like this:

class UserViewModel(private val app: Application) : AndroidViewModel(app){
val user = MutableLiveData<String>()

...

private fun startGetUserService() {
    val intent = Intent(app, MyIntentService::class.java)
    intent.putExtra(...)
    app.startService(intent)
}

@Subscribe
fun handleSuccess(event: UserCallback.Success) {
    user.value = event.user
}
}

Robolectric Test:

@RunWith(RobolectricTestRunner.class)
public class Test {
@Test
public void testUser() {
    UserViewModel model = new UserViewModel(RuntimeEnvironment.application)
    // how do I test that startGetUserService() is sending
    // the Intent to MyIntentService and check the extras?
}

Answer:

I would rather create a mock of your Application class because then it could be used to verify which methods were called on it and which object were passed to those methods. So, it could be like this (in Kotlin):

@RunWith(RobolectricTestRunner::class)
class Test {
    @Test
    public void testUser() { 
        val applicationMock = Mockito.mock(Application::class.java)
        val model = new UserViewModel(applicationMock)
        model.somePublicMethod();

        // this will capture your intent object 
        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
        // verify startService is called and capture the argument
        Mockito.verify(applicationMock, times(1)).startService(intentCaptor.capture())

        // extract the argument value
        val intent = intentCaptor.value
        Assert.assertEquals(<your expected string>, intent.getStringExtra(<your key>))
    }
}

Question:

Background:

I have a simple application that fetches movie list using rests API call. The project structure is given below,

Activity -> ViewModel -> Repository -> ApiService (Retrofit Interface)
  1. The activity subscribes to a LiveData and listens for events changes

  2. The ViewModel hosts the MediatorLiveData observed by the activity. Initially the ViewModel sets a Resource.loading(..) value in MediatorLiveData.

  3. The ViewModel then calls the repository to get the movie list from ApiService

  4. The ApiService returns a LiveData of either Resource.success(..) or Resource.error(..)

  5. The ViewModel then merges LiveData result from ApiService in MediatorLiveData

My Queries:

Inside the unit test, only the first emit Resource.loading(..) is made by MediatorLiveData from ViewModel. The MediatorLiveData never emits any data from the repository.

ViewModel.class

private var discoverMovieLiveData: MediatorLiveData<Resource<DiscoverMovieResponse>> = MediatorLiveData()

fun observeDiscoverMovie(): LiveData<Resource<DiscoverMovieResponse>> {
        return discoverMovieLiveData
    }

fun fetchDiscoverMovies(page: Int) {

        discoverMovieLiveData.value = Resource.loading(null) // this emit get observed immediately 

        val source = movieRepository.fetchDiscoverMovies(page)
        discoverMovieLiveData.addSource(source) {
            discoverMovieLiveData.value = it // never gets called
            discoverMovieLiveData.removeSource(source)
        }
    } 

Repository.class

fun fetchDiscoverMovies(page: Int): LiveData<Resource<DiscoverMovieResponse>> {
        return LiveDataReactiveStreams.fromPublisher(
            apiService.fetchDiscoverMovies(page)
                .subscribeOn(Schedulers.io())
                .map { d ->
                    Resource.success(d) // never gets called in unit test
                }
                .onErrorReturn { e ->
                    Resource.error(ApiErrorHandler.getErrorByThrowable(e), null) // // never gets called in unit test
                }
        )
    }

Unit Test

@Test
fun loadMovieListFromNetwork() {
        val mockResponse = DiscoverMovieResponse(1, emptyList(), 100, 10)
        val call: Flowable<DiscoverMovieResponse> = successCall(mockResponse) // wraps the retrofit result inside a Flowable<DiscoverMovieResponse>
        whenever(apiService.fetchDiscoverMovies(1)).thenReturn(call)

        viewModel.fetchDiscoverMovies(1)

        verify(apiService).fetchDiscoverMovies(1)
        verifyNoMoreInteractions(apiService)

        val liveData = viewModel.observeDiscoverMovie()
        val observer: Observer<Resource<DiscoverMovieResponse>> = mock()
        liveData.observeForever(observer)

        verify(observer).onChanged(
            Resource.success(mockResponse) // TEST FAILS HERE AND GETS "Resource.loading(null)" 
        )
    }

Resource is a generic wrapper class that wraps data for different scenario e.g. loading, success, error.

class Resource<out T>(val status: Status, val data: T?, val message: String?) {
.......
}

EDIT: #1

For testing purpose, I've updated my rx thread in repository to run it on main thread. This ends up with a Looper not mocked exception.

fun fetchDiscoverMovies(page: Int): LiveData<Resource<DiscoverMovieResponse>> {
            return LiveDataReactiveStreams.fromPublisher(
                apiService.fetchDiscoverMovies(page)
                    .subscribeOn(AndroidSchedulers.mainThread())
                    .map {...}
                    .onErrorReturn {...}
            )
        }

In test class,

@ExtendWith(InstantExecutorExtension::class)
class MainViewModelTest {

    companion object {
        @ClassRule
        @JvmField
        val schedulers = RxImmediateSchedulerRule()
    }

    @Test
        fun loadMovieListFromNetwork() {
        .....  
       }
}

}

RxImmediateSchedulerRule.class

class RxImmediateSchedulerRule : TestRule {

    private val immediate = object : Scheduler() {
        override fun createWorker(): Worker {
            return ExecutorScheduler.ExecutorWorker(Executor { it.run() })
        }
    }

    override fun apply(base: Statement, description: Description): Statement {
        return object : Statement() {
            @Throws(Throwable::class)
            override fun evaluate() {
                RxJavaPlugins.setInitIoSchedulerHandler { immediate }
                RxJavaPlugins.setInitComputationSchedulerHandler { immediate }
                RxJavaPlugins.setInitNewThreadSchedulerHandler { immediate }
                RxJavaPlugins.setInitSingleSchedulerHandler { immediate }
                RxAndroidPlugins.setInitMainThreadSchedulerHandler { immediate }

                try {
                    base.evaluate()
                } finally {
                    RxJavaPlugins.reset()
                    RxAndroidPlugins.reset()
                }
            }
        }
    }

}

InstantExecutorExtension.class

class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {

    override fun beforeEach(context: ExtensionContext?) {
        ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
            override fun executeOnDiskIO(runnable: Runnable) {
                runnable.run()
            }

            override fun postToMainThread(runnable: Runnable) {
                runnable.run()
            }

            override fun isMainThread(): Boolean {
                return true
            }
        })
    }

    override fun afterEach(context: ExtensionContext?) {
        ArchTaskExecutor.getInstance().setDelegate(null)
    }

}

Answer:

I think all you need to do is change

val call: Flowable<DiscoverMovieResponse> = successCall(mockResponse)

to

val call: Flowable<DiscoverMovieResponse> = Flowable.just(mockResponse)

And make use of the LiveDataUtil class from the architecture components google sample. So you'll need to copy/paste that into your project.

So at the end of the day your new test would look like this (assuming all the associations and mocks are setup correctly at the top of the test class). Also you're using an InstantExecutorExtension like azizbekian showed you above.

@Test
fun loadMovieListFromNetwork() {
    val mockResponse = DiscoverMovieResponse(1, emptyList(), 100, 10)
    val call: Flowable<DiscoverMovieResponse> = Flowable.just(mockResponse)
    whenever(apiService.fetchDiscoverMovies(1)).thenReturn(call)

    viewModel.fetchDiscoverMovies(1)

    assertEquals(Resource.success(mockResponse), LiveDataTestUtil.getValue(viewModel.discoverMovieLiveData))
}

If that test passes, it means you were able to successfully observe the results of a network request and return a successful response.

Question:

I have a View Model that extends AndroidViewModel

class MoveViewModel(application: Application): AndroidViewModel(application),CoroutineScope{
    ....
}

And I want to unit test it but I cannot figure out how to Mock the Application class

@Test
    fun testSearchDataValidation() {
        val application = Mockito.mock(Application::class.java)
        val viewModel = MoveViewModel(application)

        .....
    }

But when I go to run the test I get an error that Mockito cannot mock Application

org.mockito.exceptions.base.MockitoException: Mockito cannot mock this class: class android.app.Application.

Mockito can only mock non-private & non-final classes.

How do I mock the Application class to pass it to my view model?

Edit:

Here is my folder hierarchy as suggested by @farhanjk


Answer:

Mockito.mock(Application::class.java)

In your test folder, create a hierarchy like following:

In the org.mockito.plugins.MockMaker file, just put a one-liner text mock-maker-inline.

Mock the unmockable: opt-in mocking of final classes/methods

Question:

There's actually a couple of similar questions here, but I've tried all the suggestions, and still facing this problem.

Wanted but not invoked:
observer.onChanged(<Repo matcher>);
-> at xxx.RepoViewModelTest.testRepoList(RepoViewModelTest.kt:51)
Actually, there were zero interactions with this mock.

class RepoViewModelTest {
    @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule()
    @Mock private lateinit var observer: Observer<DataWrapper<List<Repo>>>
    @Mock private lateinit var repoRepository: RepoRepository
    private lateinit var repoViewModel: RepoViewModel

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        repoViewModel = RepoViewModel(RepoUseCase(repoRepository))
    }

    @Test
    fun testError() {
        `when`(repoRepository.getRepos("mine"))
            .thenReturn(Observable.error(Exception("No internet")))
        repoViewModel.observeRepos().observeForever(observer)
        repoViewModel.getRepos("mine")    
        verify(observer).onChanged(
            argThat(RepoMatcher(DataWrapper(Exception("No internet")))))
    }

    @Test
    fun testRepoList() {
        val listIn = listOf(Repo("Repo1"), Repo("Repo2"))
        `when`(repoRepository.getRepos("mine"))
            .thenReturn(Observable.just(listIn))
        repoViewModel.observeRepos().observeForever(observer)
        repoViewModel.getRepos("mine")
        val listOut = listOf(Repo("Repo1"), Repo("Repo2"))
        verify(observer)
            .onChanged(argThat(RepoMatcher(DataWrapper(listOut))))
    }
}

I can run each test individually just fine, but not the whole suite. I've tried to reset the mocks after each test, create one observer per test, or instantiate each mock object inside each test, no dice.

Running tests in Android Studio 3.2.1, under app/xxx/test

mockito 2.8.47

java 1.8.0_191


Answer:

Probably you need something like this

@BeforeClass
public static void setupClass() {
    RxAndroidPlugins.setInitMainThreadSchedulerHandler(__ -> Schedulers.trampoline());
}

Question:

i'm try to test my ViewModel with mockito.

This is my TestClass:

@RunWith(JUnit4::class)
class RatesViewModelTest {

    @Rule @JvmField
    open val instantExecutorRule = InstantTaskExecutorRule()

    @Mock
    var observer: Observer<Pair<ArrayList<CurrencyExchangerModel>,Boolean>>? = null

    @Mock
    private lateinit var repository: RatesRepository

    private var currencyList = ArrayList<CurrencyModel>()

    private lateinit var viewModel : RatesViewModel

    @Before
    fun setUp(){
        MockitoAnnotations.initMocks(this)

        currencyList.add(CurrencyModel("BASE"))
        viewModel = RatesViewModel(repository!!)
        viewModel.getCurrencyExchangerObservableList().observeForever(observer!!)
    }

    @Test
    fun testNull(){
        Mockito.`when`(repository.getFlowableRates()).thenReturn( Flowable.just(currencyList) )
        assertTrue(viewModel.getCurrencyExchangerObservableList().hasObservers())

    }
}

When this method is invoked:

Mockito.`when`(repository.getFlowableRates()).thenReturn( Flowable.just(currencyList) )

I got this error:

kotlin.UninitializedPropertyAccessException: lateinit property db has not been initialized

Here the repository:

open class RatesRepository(context:Context) : BaseRepository(context){

    @Inject
    lateinit var ratesAPI: RatesAPI

    @Inject
    lateinit var db: Database

    /**
     * load the updated chatList from API
     */
    fun loadCurrencyRatesFromAPI(): Single<ArrayList<CurrencyModel>> {
        val supportedCurrency = context.resources.getStringArray(R.array.currencies)
        return ratesAPI.getLatestRates(EUR_CURRENCY_ID).map { RatesConverter.getRatesListFromDTO(it,supportedCurrency) }
    }

    /**
     * save rates on DB
     */
    fun saveCurrencyRatesOnDB(list:ArrayList<CurrencyModel>): Completable {
        return db.currencyRatesDAO().insertAll(list)
    }

    /**
     * get flawable rates from DB
     */
    fun getFlowableRates(): Flowable<List<CurrencyModel>> {
        return db.currencyRatesDAO().getAll()
    }

    companion object {
        const val EUR_CURRENCY_ID = "EUR" //BASE
    }
}

What i'm doing wrong ?

Thx !


Answer:

When you define behaviour of a mock and use the when(...).then(...) notation of mockito, the method itself is invoked (by mockito, normally not relevant for your test).

In your case that is a problem because db is not initialized.

To avoid this issues use the doReturn(...).when(...) syntax in these cases, which does not cause the method invocation.

Mockito.doReturn(Flowable.just(currencyList)).when(repository).getFlowableRates();

(You might need to adjust this java syntax to make it kotlin compatible)

Question:

I have a method in my SearchViewModel and I want to test this method by Mockito and JUnit4.(searchCity()) but this error is shown after running the test:

kotlin.UninitializedPropertyAccessException: lateinit property mRepository has not been initialized

SearchViewModel class :

class SearchViewModel @Inject constructor() : BaseViewModel() {

    @Inject
    lateinit var mRepository: DataRepository
    @Inject
    lateinit var sharedPfs: SharedPrefs

    private var disposable: Disposable? = null
    val search = MutableLiveData<ResponseSearch>()
    val searchOWM = MutableLiveData<ResponseOWMCity>()
    val searchCityName = MutableLiveData<String>()
    val serachClick = SingleLiveEvent<Boolean>()
    val progressBar = SingleLiveEvent<Boolean>()
    val searchOWMvisibility = SingleLiveEvent<Boolean>()
    val cityOWMclick = SingleLiveEvent<ResponseOWMCity>()

    override fun getSharedPrefs(): SharedPrefs? {
        return sharedPfs
    }

    fun stop() {
        disposable?.let { if (!it.isDisposed) it.dispose() }
    }

    fun fabSearchClick(){
        serachClick.call()
    }

    fun searchCity() {
        val cityName = searchCityName.value
        if (!Strings.isEmptyOrWhitespace(cityName)) {
            progressBar.postValue(true)
            disposable = mRepository.doSearchProcess(cityName)
                ?.subscribe({
                    search.postValue(it)
                    progressBar.postValue(false)
                }, {
                    showToast(it!!.message!!)
                    progressBar.postValue(false)
                })
        } else{
            showToast("لطفا شهر دلخواه خود را وارد کنید.")
        }
    }


    fun searchCityOWM() {
        val cityName = searchCityName.value
        disposable = mRepository.doSearchProcessOWM(cityName)
            ?.subscribe({
                if (it != null){
                    searchOWM.postValue(it)
                    searchOWMvisibility.postValue(true)
                } else{
                    searchOWMvisibility.postValue(false)
                }

            }, {
                searchOWMvisibility.postValue(false)
            })

    }

    fun clickCityOWM(city: ResponseOWMCity){
        cityOWMclick.postValue(city)
    }
}

DataRepository class :

class DataRepository @Inject
    constructor(private val endPointAPI: EndPointAPI, private val localRoomDatabse: LocalRoomDatabse) {

    fun getAllSavedResults(): LiveData<List<City?>>? {
        return localRoomDatabse.roomDao().getAllResults()
    }

    fun doSearchProcess(city: String?): Observable<ResponseSearch>? {
        return endPointAPI.searchCities(Config.BASE_URL2 + city)
            .subscribeOn(Schedulers.io())
            ?.observeOn(AndroidSchedulers.mainThread())
            ?.doOnError({ throwable -> Log.i("1397", "remote: " + throwable.message) })
    }
}

SearchViewModelTest :

class SearchViewModelTest {

    @get:Rule
    val mockitoRule: MockitoRule = MockitoJUnit.rule()

    @get:Rule
    val taskExecutorRule = InstantTaskExecutorRule()

    @Rule
    @JvmField
    var testSchedulerRule = RxImmediateSchedulerRule()

    @Mock
    lateinit var observer: Observer<ResponseSearch>

    @Mock
    lateinit var mRepository: DataRepository

    lateinit var searchViewModel: SearchViewModel


    @Before
    @Throws(Exception::class)
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        searchViewModel = SearchViewModel()
    }


    @Test
    fun doSearchResultSuccessWithData() {

        // GIVEN
        val res = RESULT()
        res.name = "shiraz"
        val list = ArrayList<RESULT>()
        list.add(res)
        val search = ResponseSearch(list)
        val observable = Observable.just(search)


        // WHEN
        searchViewModel.searchCityName.value = "shiraz"
        searchViewModel.search.observeForever(observer)
        whenever(mRepository?.doSearchProcess("shiraz")).thenReturn(observable)
        searchViewModel.searchCity()
        // THEN
        assertNotNull(searchViewModel.search.value)
        assertThat(searchViewModel.search.value?.results?.size, CoreMatchers.`is`(1))

    }
}

can anyone help me?


Answer:

While using Dagger and field injection, you should actually inject them with component or factory when it comes to ViewModels. While testing you can inject that mock you created here

@Mock lateinit var mRepository: DataRepository

using auto-generated code by Dagger

@Before
@Throws(Exception::class)
fun setUp() {
    MockitoAnnotations.initMocks(this)
    searchViewModel = SearchViewModel()
    SearchViewModel_MembersInjector.injectMRepository(searchViewModel, mRepository)
}