Unit Testing ViewModel part 2

Kishan Maurya
MindOrks
Published in
3 min readMay 28, 2019

In my previous tutorial, We have discussed unit testing of ViewModel. This tutorial is independent of the previous one but if you want then you can read from below link
https://medium.com/mindorks/unit-testing-for-viewmodel-19f4d76b20d4

Previously we have discussed ViewState and observing those view states using live data. In this, we will observe live data directly instead of creating view states.

In unit testing, we have to follow the AAA rule

A: Assemble/Arrange ( What data to be expected, What data we will get when we make API call)
A: Act ( What we will do when we get data, like loading true/false, set data, set error, etc)
A: Assert/Verify ( verify or assert data expected, sequence )

ViewModel class

public final class SearchViewModel extends ViewModel {
private final MutableLiveData<ProductSearch> searchData = new MutableLiveData<>();
private final MutableLiveData<Boolean> isLoadingData = new MutableLiveData<>();
private final MutableLiveData<Throwable> errorData = new MutableLiveData<>();
private final Api api;
private final UserManager userManager;
private Disposable searchDisposable;@Inject SearchViewModel(final Api api, final UserManager userManager) {
this.api = api;
this.userManager = userManager
}@Override protected void onCleared() {
RxUtils.disposeIfNeeded(searchDisposable);
super.onCleared();
}
LiveData<ProductSearch> getSearchData() {
return searchData;
}
LiveData<Boolean> getIsLoadingData() {
return isLoadingData;
}
LiveData<Throwable> getErrorData() {
return errorData;
}
private void fetchSearchData() {
if (!RxUtils.inFlight(searchDisposable)) {
searchDisposable = api.search()
.doOnNext((results) -> isLoadingData.postValue(false))
.doOnError((error) -> isLoadingData.postValue(false))
.doOnSubscribe((subscribe) -> isLoadingData.postValue(true));
})
.compose(applySchedulers())
.subscribe(searchData::setValue,errorData::setValue);
}
}
private <T> ObservableTransformer<T, T> applySchedulers() {
return observableSource -> observableSource
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}

SearchViewModelTest class

public class SearchViewModelTest {

@Rule public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();

@Mock Api api;
@Mock UserManager userManager;

@Mock Observer<ProductSearch> dataObserver;
@Mock Observer<Boolean> loadingObserver;
@Mock Observer<Throwable> throwableObserver;

private SearchViewModel viewModel;

@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
viewModel = new SearchViewModel(api,userManager);
}

@Test
public void testFetchSearchData_whenReturnsData() {

//Assemble
ProductSearch productSearch = new ProductSearch();
when(api.search()).thenReturn(Single.just(productSearch));

//Act
viewModel.getSearchData().observeForever(dataObserver);
viewModel.getIsLoadingData().observeForever(loadingObserver);

viewModel.fetchSearchData();

//Verify
verify
(loadingObserver).onChanged(true);
verify(dataObserver).onChanged(productSearch);
verify(loadingObserver).onChanged(false);
}

@Test
public void testFetchSearchData_whenThrowsError() {

//Assemble
Throwable throwable = new Throwable("Exception");
when(api.search()).thenReturn(Single.error(throwable));

//Act
viewModel.getSearchData().observeForever(dataObserver);
viewModel.getIsLoadingData().observeForever(loadingObserver);
viewModel.getErrorData().observeForever(throwableObserver);

viewModel.fetchSearchData();

//Verify
verify
(loadingObserver).onChanged(true);
verify(throwableObserver).onChanged(throwable);
verify(loadingObserver).onChanged(false);
}

@After
public void tearDown() throws Exception {
api = null;
userManager = null;
}
}

If you get below error
Caused by: java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details. at android.os.Looper.getMainLooper(Looper.java)

then init RxImmediateSchedulerRule

Please go through below link
https://gist.github.com/starkej2/c446012d540b8e25409fa525c6d17cb1

https://medium.com/@PaulinaSadowska/writing-unit-tests-on-asynchronous-events-with-rxjava-and-rxkotlin-1616a27f69aa

RxImmediateSchedulerRule

class RxImmediateSchedulerRule : TestRule {
private val immediate = object : Scheduler() {
override fun scheduleDirect(run: Runnable, delay: Long, unit: TimeUnit): Disposable {
return super.scheduleDirect(run, 0, unit)
}

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 { scheduler -> immediate }
RxJavaPlugins.setInitComputationSchedulerHandler { scheduler -> immediate }
RxJavaPlugins.setInitNewThreadSchedulerHandler { scheduler -> immediate }
RxJavaPlugins.setInitSingleSchedulerHandler { scheduler -> immediate }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> immediate }

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

Now Our view model test class will look like. Add the following line in your test class
@Rule public RxImmediateSchedulerRule timeoutRule = new RxImmediateSchedulerRule();



@Rule public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
@Rule public RxImmediateSchedulerRule timeoutRule = new RxImmediateSchedulerRule();

Other Links for View Model Testing

https://medium.com/mindorks/unit-testing-for-viewmodel-19f4d76b20d4
https://medium.com/mindorks/effective-livedata-testing-13d17b555d9b

Soon I will post more articles on android, core java.
till then happy learning… keep reading… :)

Photo by Raj Eiamworakul on Unsplash

--

--