Android ViewModel unit test : RxJava onSuccess gives nullPointerException - android

I'm trying to unit test my viewModel class but when I run the test I get a NullPointerException in my disposable OnSuccess Method and I don't understand why. Because of this the method that I test always returns null.
Here is my code for my test class CityListViewModelTest.kt:
#RunWith(JUnit4::class)
class CityListViewModelTest {
#Rule
#JvmField
val rule = InstantTaskExecutorRule()
#Mock
private lateinit var repository: ForecastRepository
#InjectMocks
private lateinit var viewModel: CityListViewModel
#Before #Throws fun setUp(){
RxAndroidPlugins.setInitMainThreadSchedulerHandler{Schedulers.trampoline()}
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
MockitoAnnotations.initMocks(this)
}
#Test
fun getCities() {
val response = getMockedCities(5)
`when`(repository.getCities(ArgumentMatchers.anyDouble(), ArgumentMatchers.anyDouble()))
.thenReturn(Single.just(response))
val result = viewModel.getCities(0.0,0.0)
verify(repository).getCities(0.0,0.0)
verify(repository).getCache() //should be called but isn't
assertEquals(response.list,result.value) //result.value should be a list of 5 mocked cities but is null
}
fun getMockedCities(count : Int) : OpenWeatherCycleDataResponse {
val cities = ArrayList<City>()
for (i in 0..count) {
val city = mock(City::class.java)
cities.add(city)
}
return OpenWeatherCycleDataResponse(cities)
}
}
And my viewModel class CityListViewModel.kt :
class CityListViewModel #Inject constructor(private var forecastRepo: ForecastRepository):ViewModel() {
//#Inject lateinit
var cities : MutableLiveData<List<City>> = MutableLiveData()
//#Inject lateinit
var disposable : CompositeDisposable = CompositeDisposable()
fun getCities(lat: Double,lon:Double): LiveData<List<City>> {
disposable.add(forecastRepo.getCities(lat,lon).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object: DisposableSingleObserver<OpenWeatherCycleDataResponse>(){
override fun onSuccess(t: OpenWeatherCycleDataResponse) {
forecastRepo.getCache().saveCities(t.list)
cities.value = t.list
}
override fun onError(e: Throwable) {
Timber.e(e.localizedMessage)
}
}))
return cities
}
fun getCityByName(cityName: String): LiveData<City>{
val searchedCity = MutableLiveData<City>()
disposable.add(forecastRepo.getCityByName(cityName).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object: DisposableSingleObserver<City>(){
override fun onSuccess(t: City) {
searchedCity.value = t
forecastRepo.getCache().saveCities(listOf(t))
}
override fun onError(e: Throwable) {
Timber.e(e.localizedMessage)
}
}))
return searchedCity
}
override fun onCleared() {
super.onCleared()
disposable.clear()
}
}
Here are the logs :
java.lang.NullPointerException
at com.example.zach.weatherapp.viewModel.CityListViewModel$getCities$1.onSuccess(CityListViewModel.kt:30)
at com.example.zach.weatherapp.viewModel.CityListViewModel$getCities$1.onSuccess(CityListViewModel.kt:27)
at io.reactivex.internal.operators.single.SingleObserveOn$ObserveOnSingleObserver.run(SingleObserveOn.java:81)
at io.reactivex.internal.schedulers.TrampolineScheduler.scheduleDirect(TrampolineScheduler.java:52)
at io.reactivex.internal.operators.single.SingleObserveOn$ObserveOnSingleObserver.onSuccess(SingleObserveOn.java:64)
at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.onSuccess(SingleSubscribeOn.java:68)
at io.reactivex.internal.operators.single.SingleJust.subscribeActual(SingleJust.java:30)
at io.reactivex.Single.subscribe(Single.java:3096)
at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
at io.reactivex.internal.schedulers.TrampolineScheduler.scheduleDirect(TrampolineScheduler.java:52)
at io.reactivex.internal.operators.single.SingleSubscribeOn.subscribeActual(SingleSubscribeOn.java:37)
at io.reactivex.Single.subscribe(Single.java:3096)
at io.reactivex.internal.operators.single.SingleObserveOn.subscribeActual(SingleObserveOn.java:35)
at io.reactivex.Single.subscribe(Single.java:3096)
at io.reactivex.Single.subscribeWith(Single.java:3140)
at com.example.zach.weatherapp.viewModel.CityListViewModel.getCities(CityListViewModel.kt:27)
at com.example.zach.weatherapp.viewModel.CityListViewModelTest.getCities(CityListViewModelTest.kt:58)

As soon as this line gets called:
// CityListViewModelTest
val result = viewModel.getCities(0.0,0.0)
CityListViewModel will subscribe to forecastRepo.getCities(), so it makes sense that verify(repository).getCities(0.0,0.0) passes.
However it is not guaranteed that forecastRepo.getCache() will be called before verify(repository).getCache() because forecastRepo.getCities() runs on a separate thread. In your test code, you need to use TestSchedulers to wait for the operation in io scheduler to complete.
Side note:
It seems .observeOn(AndroidSchedulers.mainThread()) in ViewModels doesn't do much here because ViewModels are independent of Android lifecycles. Instead of using setValue(), you can use postValue() to update MutableLiveData from a background thread.
Update:
Try using this:
#Rule
#JvmField
val rule = InstantTaskExecutorRule()
#Mock
lateinit var observer: Observer<List<City>>
#Test
fun getCities() {
val response = getMockedCities(5)
`when`(repository.getCities(ArgumentMatchers.anyDouble(), ArgumentMatchers.anyDouble()))
.thenReturn(Single.just(response))
viewModel.getCities(0.0,0.0).observeForever(observer)
verify(repository).getCities(0.0,0.0)
verify(repository).getCache()
// assertEquals(response.list,result.value) //result.value should be a list of 5 mocked cities but is null
verify(observer).onChanged(reponse.list)
}
Also getCache() and getCache().saveCities() could be the problem. Try mocking these as well if above code doesn't work.

Problem solved by creating by hand a dummy response. getMockedCities() returns an array of City objects with null variables (as expected) but my City object's variables weren't nullable. Also I had to mock the repository.getCache()

Related

Mocked multiple times, but mocked value does not change after secondly mocked in different test method

I'm writing unit test for my ViewModel. I have mocked my data source and want to test that datasource returns success and error cases. If i run tests individually everything is OK.
In the first method i mocked to return success, in the second method i mocked to return error. When i run these 2 tests together (by clicking run tests in class name), in the second method i want dataSource.getPackageCard() to return ResponseState.Error("error1337") however it returns ResponseState.Success(responseDto). In other words, it remembers the mocked value from 1st method. Why ? How to solve that problem ?
#MediumTest
#RunWith(AndroidJUnit4::class)
#ExperimentalCoroutinesApi
class MyViewModelTest {
#get: Rule
var instantExecutorRule = InstantTaskExecutorRule()
#get: Rule
var mainCoroutineRule = MainCoroutineRule()
private lateinit var viewModel: MyViewModel
lateinit var MyRepository: MyRepository
val responseDto = MyResponseDto().apply {
val myList = mutableListOf<CardListGroupDTO>()
myList.add(CardListGroupDTO(cardGroupType = "test",
headerTitle = "test",
buttonAll = ButtonDto(title = "test", url = "test")
))
groupList = myList
}
#MockK
lateinit var dataSource: MyDataSource
#Before
fun setup() {
MockKAnnotations.init(this)
MyRepository = MyRepositoryImpl.getInstance(dataSource)
viewModel = MyViewModel(MyRepository)
}
#After
fun afterTests() {
unmockkAll()
unmockkObject(dataSource)
}
#Test
fun `test successful case`() = runBlockingTest {
// given
coEvery {
dataSource.getPackageCard()
} returns ResponseState.Success(
responseDto
)
var counter = 0
viewModel.MyResponseDto.observeForever(
object : Observer<ResponseState<MyResponseDto>> {
override fun onChanged(t: ResponseState<MyResponseDto>) {
// println(viewModel.MyResponseDto.value)
when (counter) {
0 ->
Truth.assertThat(t).isEqualTo(ResponseState.Loading(true))
1 ->
Truth.assertThat(t).isEqualTo(ResponseState.Success(responseDto))
2 -> {
Truth.assertThat(t).isEqualTo(ResponseState.Loading(false))
viewModel.MyResponseDto.removeObserver(this)
}
}
counter++
}
})
viewModel.getPackageCard()
}
#Test
fun `test error case`() = runBlockingTest {
val errorMessage = "error1337"
// given
coEvery {
dataSource.getPackageCard()
} returns ResponseState.Error(
errorMessage
)
var counter = 0
viewModel.MyResponseDto.observeForever(
object : Observer<ResponseState<MyResponseDto>> {
override fun onChanged(t: ResponseState<MyResponseDto>) {
// println(viewModel.MyResponseDto.value)
when (counter) {
0 ->
Truth.assertThat(t).isEqualTo(ResponseState.Loading(true))
1 ->
Truth.assertThat(t).isEqualTo(ResponseState.Error(errorMessage))
2 -> {
Truth.assertThat(t).isEqualTo(ResponseState.Loading(false))
viewModel.MyResponseDto.removeObserver(this)
}
}
counter++
}
})
viewModel.getPackageCard()
}
}
I found the answer finally. Since i use static repository MyRepositoryImpl.getInstance(dataSource), the datasource is mocked once. Second mock is not valid. I did manual singleton, inside companioan object create if it is not null, if nonnull return the object. This is the cause of my problem.
I solved the problem, by removing the singleton pattern i implemented as the above. I used constructor injection and made my repository singleton in this way. In my unit tests my repository is not singleton any more.
#Singleton
class MyRepositoryImpl #Inject constructor(
private val myRemoteDataSource: MyRemoteDataSource
) : MyRepository
And my viewmodel test is fixed when i write the following :
#Before
fun setup() {
MockKAnnotations.init(this)
myRepository = MyRepositoryImpl(dataSource)
viewModel = MyViewModel(myRepository)
}

How to unit test view interface method called in Presenter with coroutines on Android?

I'm working on Android for a while but it's the first time I have to write some unit tests.
I have a design pattern in MVP so basically I have my Presenter, which have a contract (view) and it's full in kotlin, using coroutines.
Here is my Presenter class : The Repository and SomeOtherRepository are kotlin object so it's calling methods directly (The idea is to not change the way it's working actually)
class Presenter(private val contractView: ContractView) : CoroutinePresenter() {
fun someMethod(param1: Obj1, param2: Obj2) {
launch {
try {
withContext(Dispatchers.IO) {
val data = SomeService.getData() ?: run { throw Exception(ERROR) } // getData() is a suspend function
Repository.doRequest(param1, param2) // doRequest() is a suspend function also
}.let { data ->
if (data == null) {
contractView.onError(ERROR)
} else {
if (SomeOtherRepository.validate(data)) {
contractView.onSuccess()
} else {
contractView.onError(ERROR)
}
}
} catch (exception: Exception) {
contractView.onError(exception)
}
}
}
}
So the goal for me is to create unit test for this Presenter class so I created the following class in order to test the Presenter. Here is the Test implementation :
I read a lot of articles and stackoverflow links but still have a problem.
I setup a TestCoroutineRule which is like this :
#ExperimentalCoroutinesApi
class TestCoroutineRule(
private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : TestWatcher(), TestCoroutineScope by TestCoroutineScope() {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
private fun TestCoroutineRule.runBlockingTest(block: suspend () -> Unit) =
testDispatcher.runBlockingTest { block() }
}
And here is the PresenterTest implementation :
#ExperimentalCoroutinesApi
class PresenterTest {
#get:Rule
val testCoroutineRule = TestCoroutineRule()
#Mock
private lateinit var view: ContractView
#Mock
private lateinit var repository: Repository
private lateinit var presenter: Presenter
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
presenter = Presenter(view)
}
#Test
fun `test success`() =
testCoroutineRule.runBlockingTest {
// Given
val data = DummyData("test", 0L)
// When
Mockito.`when`(repository.doRequest(param1, param2)).thenReturn(data)
// Then
presenter.someMethod("test", "test")
// Assert / Verify
Mockito.verify(view, Mockito.times(1)).onSuccess()
}
}
The problem I have is the following error Wanted but not invoked: view.onSuccess(); Actually there were zero interactions with this mock.
The ContractView is implemented in the Activity so I was wondering if I have to use Robolectric in order to trigger the onSuccess() method within the Activity context. I also think that I have a problem regarding the usage of coroutines maybe. I tried a lot of things but I always got this error on the onSuccess et onError view, if anyone could help, would be really appreciated :)
There could be other problems, but at a minimum you are missing:
Mockito.`when`(someOtherRepository.validate(data)).thenReturn(data)
Mockito.`when`(someService.getData()).thenReturn(data)
Use your debugger and check your logs to inspect what the test is doing

Unit testing LiveData observerForever results in NullPointer Exception with Junit5

I am using Android databinding to listen to live data changes and I would like to observe changes on the viewmodel level (Rather then observing on fragment and then sending a callback to the viewmodel)
The observerForever is interesting as it serves the purpose for me. However when I run a test I get the following error:
java.lang.NullPointerException
at androidx.arch.core.executor.DefaultTaskExecutor.isMainThread(DefaultTaskExecutor.java:77)
at androidx.arch.core.executor.ArchTaskExecutor.isMainThread(ArchTaskExecutor.java:116)
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:461)
at androidx.lifecycle.LiveData.observeForever(LiveData.java:222)
at com.bcgdv.ber.maha.login.ui.LoginViewModel.<init>(LoginViewModel.kt:43)
at com.bcgdv.ber.maha.login.ui.LoginViewModelTest.<init>(LoginViewModelTest.kt:26)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.junit.platform.commons.util.ReflectionUtils.newInstance(ReflectionUtils.java:443)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:60)
My code is as follows in the viewmodel class:
val observerEmail: Observer<String> = Observer {
setEmailError(it)
checkLoginButton()
}
var email = MutableLiveData<String>()
init {
email.observeForever(observerEmail)
}
Also to note is I am using Junit5.
#ExtendWith(InstantTaskExecutorExtension::class)
class LoginViewModelTest {
val emailAddress = "xyz#xyz.com"
val password = "password"
val user: User = User("1", "xyz#xyz.com", "password")
val loginUsecase: LoginUseCase = mock {
on { loginUser(emailAddress, password) } doReturn (Single.just(user))
}
private val loginViewModel: LoginViewModel = LoginViewModel(
loginUsecase,
LoginCredentialsValidator(),
Schedulers.trampoline(),
Schedulers.trampoline()
)
#Test
fun should_return_user_as_null_initially() {
whenever(loginUsecase.getUser()).thenReturn(null)
loginViewModel.init()
assertEquals(
expected = null,
actual = loginViewModel.obsEmail.get()
)
}}
And this is the InstantTaskExecutorExtension.
class InstantTaskExecutorExtension : 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 = true
})
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
In general it's recommended to use LiveData only for View Model <-> View communication, however I think the issue is:
private val loginViewModel: LoginViewModel = LoginViewModel(
...
)
Because since this is a member variable it would be executed before the test and it's already implicitly executing init() since you call the constructor.
No need to call init() explicitly. I'd remove the loginViewModel member variable and instantiate it in the test function via the constructor:
#Test
fun should_return_user_as_null_initially() {
...
LoginViewModel(
...
)
...
}

How to Unit Test MVVM with Koin?

How to Unit Test MVVM with Koin ?
i've try to testing : link
But, i don't know why i get error("No Data in ViewModel") in ViewModelTest fun getLookUpLeagueList()
Repository
class LookUpLeagueRepository {
fun getLookUpLeague(idLeague: String): MutableLiveData<LookUpLeague> {
val lookUpLeague = MutableLiveData<LookUpLeague>()
APIService().getLookUpLeague(idLeague).enqueue(object : Callback<LookUpLeague> {
override fun onFailure(call: Call<LookUpLeague>, t: Throwable) {
d("TAG", "lookUpLeagueOnFailure ${t.localizedMessage}")
}
override fun onResponse(call: Call<LookUpLeague>, response: Response<LookUpLeague>) {
lookUpLeague.value = response.body()
}
})
return lookUpLeague
}
}
ViewModel
class LookUpLeagueViewModel(private val lookUpLeagueRepository: LookUpLeagueRepository) :
ViewModel() {
var lookUpLeagueList = MutableLiveData<LookUpLeague>()
fun getLookUpLeagueList(idLeague: String) {
lookUpLeagueList = lookUpLeagueRepository.getLookUpLeague(idLeague)
}
}
Module
val lookUpLeagueModule = module {
single { LookUpLeagueRepository() }
viewModel { LookUpLeagueViewModel(get()) }
}
ViewModel Test
class LookUpLeagueViewModelTest : KoinTest {
val lookUpLeagueViewModel: LookUpLeagueViewModel by inject()
val idLeague = "4328"
#get:Rule
val rule = InstantTaskExecutorRule()
#Mock
lateinit var observerData: Observer<LookUpLeague>
#Before
fun before() {
MockitoAnnotations.initMocks(this)
startKoin {
modules(lookUpLeagueModule)
}
}
#After
fun after() {
stopKoin()
}
#Test
fun getLookUpLeagueList() {
lookUpLeagueViewModel.lookUpLeagueList.observeForever(observerData)
lookUpLeagueViewModel.getLookUpLeagueList(idLeague)
val value = lookUpLeagueViewModel.lookUpLeagueList.value ?: error("No Data in ViewModel")
Mockito.verify(observerData).onChanged(value)
}
}
#Test
fun getLookUpLeagueList() {
lookUpLeagueViewModel.lookUpLeagueList.observeForever(observerData)
...
}
At this time lookUpLeagueList is an instance of MutableLiveData. Say this is MutableLiveData #1.
lookUpLeagueViewModel.getLookUpLeagueList(idLeague)
Executing the line above would call LookUpLeagueViewModel.getLookUpLeagueList function. Let's take a look inside it.
lookUpLeagueList = lookUpLeagueRepository.getLookUpLeague(idLeague)
A totally new MutableLiveData is created inside LookUpLeagueRepository. That is not the same one as the one observerData is observing. At this time lookUpLeagueViewModel.lookUpLeagueList refers to the new one, MutableLiveData #2 because you re-assigned it to var lookUpLeagueList.
val value = lookUpLeagueViewModel.lookUpLeagueList.value ?: error("No Data in ViewModel")
Therefore, you're actually querying against MutableLiveData #2 which is new, not observed, and empty. That's why value is null. Instead of declaring as var, you should make it val. Don't re-assign the variable, setValue or postValue to propagate the change.

Testing Coroutines and LiveData: Coroutine result not reflected on LiveData

I am new to testing and I wanted to learn how to test Coroutines with MVVM pattern. I just followed https://github.com/android/architecture-samples project and did a few changes (removed remote source). But when testing the ViewModel for fetching data from a repository, it keeps on failing with this error.
value of : iterable.size()
expected : 3
but was : 0
iterable was: []
Expected :3
Actual :0
Below is my test class for the ViewModel don't know what I'm missing. Also when mocking the repository I can get the expected results from it when printing taskRepository.getTasks() it just doesn't reflect on the LiveData when calling loadTasks()
ViewModelTest
#ExperimentalCoroutinesApi
#RunWith(MockitoJUnitRunner::class)
class TasksViewModelTest {
private lateinit var tasksViewModel: TasksViewModel
val tasksRepository = mock(TasksRepository::class.java)
#ExperimentalCoroutinesApi
#get:Rule
var mainCoroutineRule = TestMainCoroutineRule()
// Executes each task synchronously using Architecture Components.
#get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
#Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(tasksRepository)
}
#Test
fun whenLoading_hasListOfTasks() = runBlockingTest {
val task1 = Task("title1", "description1")
val task2 = Task("title2", "description2")
val task3 = Task("title3", "description3")
`when`(tasksRepository.getTasks()).thenReturn(Result.Success(listOf(
task1,
task2,
task3
)))
tasksViewModel.loadTasks()
val tasks = LiveDataTestUtil.getValue(tasksViewModel.tasks)
assertThat(tasks).hasSize(3)
}
}
TasksViewModel
class TasksViewModel #Inject constructor(
private val repository: TasksRepository
) : ViewModel() {
private val _tasks = MutableLiveData<List<Task>>().apply { value = emptyList() }
val tasks: LiveData<List<Task>> = _tasks
fun loadTasks() {
viewModelScope.launch {
val tasksResult = repository.getTasks()
if (tasksResult is Success) {
val tasks = tasksResult.data
_tasks.value = ArrayList(tasks)
}
}
}
}
Helper classes are listed below, I just copied the same classes from the sample project.
LiveDataTestUtil
object LiveDataTestUtil {
/**
* Get the value from a LiveData object. We're waiting for LiveData to emit, for 2 seconds.
* Once we got a notification via onChanged, we stop observing.
*/
fun <T> getValue(liveData: LiveData<T>): T {
val data = arrayOfNulls<Any>(1)
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data[0] = o
latch.countDown()
liveData.removeObserver(this)
}
}
liveData.observeForever(observer)
latch.await(2, TimeUnit.SECONDS)
#Suppress("UNCHECKED_CAST")
return data[0] as T
}
}
MainCoroutineRule
#ExperimentalCoroutinesApi
class TestMainCoroutineRule : TestWatcher(), TestCoroutineScope by TestCoroutineScope() {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(this.coroutineContext[ContinuationInterceptor] as CoroutineDispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
}
}
Turns out it was a problem with mockito, I have an older version, and I found there's a library called mockito-kotlin to simplify testing coroutines as stated here. I then chaged my code to this and It's working well.
tasksRepository.stub {
onBlocking { getTasks() }.doReturn(Result.Success(listOf(task1, task2, task3)))
}

Categories

Resources