问题
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)
}
}
回答1:
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(
...
)
...
}
来源:https://stackoverflow.com/questions/61582063/unit-testing-livedata-observerforever-results-in-nullpointer-exception-with-juni