I am unit testing my viewmodel and I always keep getting NullPointerException
.
Here is my viewmodel code -
class LoginViewModel(private val myUseCase: MyUseCase) :BaseViewModel() {
private val viewState = LoginViewState()
fun onLoginClicked() =
Transformations.map(
myUseCase.performUseCaseAction(
MyAction.LoginUser(
email,password)
)
) {
when (it) {
is MyResult.Loading -> viewState.copy(loading = true)
is MyResult.UserLoggedIn -> viewState.copy(
loading = false,
userLoggedIn = true
)
is MyResult.Error -> viewState.copy(loading = false, error = it.error)
}
}
}
Here is the MyUseCase interface code -
interface MyUseCase {
fun performUseCaseAction(action: MyAction): LiveData<MyResult>
}
Here is the unit test for the same -
@RunWith(MockitoJUnitRunner::class)
class LoginViewModelTest {
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
private lateinit var viewModel: LoginViewModel
@Mock
private lateinit var myUseCase: MyUseCase
@Mock
private lateinit var observer: Observer<LoginViewState>
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
viewModel = LoginViewModel(useCase)
}
@Test
fun login_loginClicked_userLoggedInExpected() {
//Given
val viewState = LoginViewState()
//When
val liveData1 = MutableLiveData<MyResult>()
`when`(useCase.performUseCaseAction(
MyAction.LoginUser("email","password")
)).thenReturn(liveData1)
liveData1.postValue(MyResult.UserLoggedIn)
viewModel.onLoginClicked().observeForever(observer)
//Then
verify(observer).onChanged(viewState.copy(loading = true))
verify(observer).onChanged(viewState.copy(loading = false, userLoggedIn = true))
}
}
Here is the output I get -
java.lang.NullPointerException
at androidx.lifecycle.MediatorLiveData$Source.plug(MediatorLiveData.java:141)
at androidx.lifecycle.MediatorLiveData.onActive(MediatorLiveData.java:118)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:437)
at androidx.lifecycle.LiveData.observeForever(LiveData.java:232)
at com.client.personaldiary.view.viewmodel.LoginViewModelTest.onLoginClicked(LoginViewModelTest.kt:88)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
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:44)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:74)
at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:80)
at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
To write a unit test for the GameViewModel class, you need an instance of the class so that you can call the class's methods and verify the state. In the body of the GameViewModelTest class, declare a viewModel property and assign an instance of the GameViewModel class to it.
Follow these steps to work with LiveData objects: Create an instance of LiveData to hold a certain type of data. This is usually done within your ViewModel class. Create an Observer object that defines the onChanged() method, which controls what happens when the LiveData object's held data changes.
Also, to work with mockito annotation we need to initialize the mock in the @before function. Next, write a test case to test the get all movies functions. First, mock the getAllMovies() function with Mockito. Then, call the repository function and verify the test case using the assertEquals function.
ViewModel is responsible for holding and processing all the data needed for the UI. It should never access your view hierarchy (like view binding object) or hold a reference to the activity or the fragment.
It seems about stubbing(when/thenReturn) timing problem.
This is not a problem for most sequential statement,
but when stubbing LiveData, it is tricky since
When your ViewModel and mocked MyUseCase are set up,
mocked MyUseCase is already evaluated.
However, in test method,
`when`(useCase.performUseCaseAction(
MyAction.LoginUser("email","password")
)).thenReturn(liveData1)
below method is not affected from above stubbing method,
viewModel.onLoginClicked()
so return null, therefore 'observeForever` raise the NPE.
The good practice of my thinking is that 1. ViewModel's initialization is after stubbing
`when`()...
viewModel = LoginViewModel(useCase)
or using liveData coroutine builder in your source code refer to: https://github.com/android/architecture-components-samples/tree/master/LiveDataSample
liveData { emitSource(....map { ... }) }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With