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)
The activity subscribes to a LiveData and listens for events changes
The ViewModel hosts the MediatorLiveData observed by the activity. Initially the ViewModel sets a
Resource.loading(..)
value in MediatorLiveData.The ViewModel then calls the repository to get the movie list from ApiService
The ApiService returns a LiveData of either
Resource.success(..)
orResource.error(..)
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) }