Search code examples
androidunit-testingmockitoandroid-livedataandroid-viewmodel

Unit testing the viewModel and livedata Mockito android


I am trying to implement unit test with Mockito for my View-Model, When my debugger is going to assign the value to live data it is crashing with error NullPointerException i.e. We are not bale to assign value to live data

My View-Model-Test Class

@Test
fun getNews() {

    Mockito.`when`(mainRepository.getNews()).thenReturn(Observable.just(getMockResponse()))
    //It is working good

    Mockito.`when`(networkHelper.isNetworkConnected()).thenReturn(true)
    //It is working good

    viewModel.getNews()

    assertEquals(Resource.success(getList()), viewModel.newsResponse.value)
}

My ViewModel

 val newsResponse by lazy { SingleLiveEvent<Resource<List<RowsItem>>>() }

        fun getNews() {
        if (!networkHelper.isNetworkConnected()) { //It is true as we macked in Test Clas
            newsResponse.value = Resource.error(AppError.NetworkError)
            return
        }
        newsResponse.value = Resource.loading() // Crashing here when we are assigning value to live data: newsResponse
        val api = mainRepository.getNews().map {
            it.rows
        }

        val disposable = api.subscribeOn(Schedulers.computation())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                { result -> handleResults(result) },
                { error -> handleError(error) },
            )

        compositeDisposable.add(disposable)
    }


     private fun handleResults(list: ArrayList<RowsItem>?) {
         if (list != null) {
             newsResponse.value = Resource.success(list)
 
         } else {
             newsResponse.value = Resource.error(AppError.CommonError)
         }
     }

      private fun handleError(t: Throwable?) {
          newsResponse.value = Resource.error(t?.failureAppError() ?: AppError.CommonError)
      }

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:486) at androidx.lifecycle.LiveData.setValue(LiveData.java:306) at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50) at com.lifex.proficiencyexercise.repo.utils.SingleLiveEvent.setValue(SingleLiveEvent.java:28) at com.lifex.proficiencyexercise.ui.MainActivityViewModel.getNews(MainActivityViewModel.kt:37) at com.lifex.proficiencyexercise.ui.MainActivityViewModelTest.getNews(MainActivityViewModelTest.kt:45) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.mockito.internal.runners.DefaultInternalRunner$1$1.evaluate(DefaultInternalRunner.java:54) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:99) at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:105) at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:40) at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)

Process finished with exit code -1


Solution

  • Just need to add rules in test class

    @Rule
    @JvmField
    var testSchedulerRule = RxImmediateSchedulerRule() //For Retrofit
    
    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()//For LiveData
    

    RxImmediateSchedulerRule is a custom class that extends TestRule

    class RxImmediateSchedulerRule : TestRule {
    
    private val immediateScheduler = object : Scheduler() {
        @NonNull
        override fun scheduleDirect(@NonNull run: Runnable, delay: Long, @NonNull unit: TimeUnit): Disposable {
            // Hack to prevent stack overflows in unit tests when scheduling with a delay;
            return super.scheduleDirect(run, 0, unit)
        }
    
        @NonNull
        override fun createWorker(): Worker {
           return ExecutorScheduler.ExecutorWorker(Executor { it.run() })
        }
    }
    
    @NonNull
    override fun apply(@NonNull base: Statement, @NonNull description: Description): Statement {
        return object : Statement() {
            @Throws(Throwable::class)
            override fun evaluate() {
                RxJavaPlugins.setInitIoSchedulerHandler { immediateScheduler }
                RxJavaPlugins.setInitComputationSchedulerHandler { immediateScheduler }
                RxJavaPlugins.setInitNewThreadSchedulerHandler { immediateScheduler }
                RxJavaPlugins.setInitSingleSchedulerHandler { immediateScheduler }
                RxAndroidPlugins.setInitMainThreadSchedulerHandler { immediateScheduler }
    
                try {
                    base.evaluate()
                } finally {
                    RxJavaPlugins.reset()
                    RxAndroidPlugins.reset()
                }
            }
        }
    }}
    

    Now at the end My Test Class will look like

     @RunWith(MockitoJUnitRunner::class)
     class MainActivityViewModelTest : TestCase() {
    
    
    private val networkHelper = Mockito.mock(NetworkHelper::class.java)
    
    private val mainRepository = Mockito.mock(MainRepository::class.java)
    
    private val viewModel = MainActivityViewModel(networkHelper, mainRepository)
    
    @Rule
    @JvmField
    var testSchedulerRule = RxImmediateSchedulerRule() //For Retrofit
    
    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()//For LiveData
    
    
    @Before
    public override fun setUp() {
    
    }
    
    @Test
    fun getNews() {
        Mockito.`when`(mainRepository.getNews()).thenReturn(Observable.just(getMockResponse()))
        //It is working good
        Mockito.`when`(networkHelper.isNetworkConnected()).thenReturn(true)
    
        //It is working good
        viewModel.getNews()
    
        assertEquals(Resource.success(getList()), viewModel.newsResponse.value)
    
    
    }
    
    private fun getMockResponse(): ApiResponse<ArrayList<RowsItem>> {
        return ApiResponse("Response Success", getList())
    }
    
    private fun getList(): ArrayList<RowsItem> {
        val rowsItem = RowsItem("ImageHref", "Desc", "Title")
        val list = ArrayList<RowsItem>()
        list.add(rowsItem)
        return list
    }}