Unit testing LiveData observerForever results in NullPointer Exception with Junit5

拟墨画扇 提交于 2020-05-17 07:09:06

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!