3

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)
}

}

SoH
  • 2,180
  • 2
  • 24
  • 53
  • Does this answer your question? [MutableLiveData is null in JUnitTest](https://stackoverflow.com/questions/49840444/mutablelivedata-is-null-in-junittest) – Bhavik Kasundra May 04 '20 at 03:25
  • 1
    @BhavikKasundra I tried it. I still get an error. – SoH May 04 '20 at 08:09
  • @SoH I am facing the same issue, did you find any solution for this. – Rajat Beck Jun 04 '20 at 20:37
  • I ended up not using the init. Also I am not using livedata. instead using observable fields and hooking them up with rxjava publish subjects – SoH Jun 05 '20 at 08:49
  • 1
    Can you double check, if your `@Test` annotation comes from the JUnit5 jupiter packages? That happened to me... – MVO May 23 '21 at 15:14

1 Answers1

0

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(
       ...
    )
    ...
}
Michael Troger
  • 3,336
  • 2
  • 25
  • 41
  • Made changes to my answer. I was already using a task extension. Still get the same issue. I am using observeForever in the viewmodel itself. in the init method. – SoH May 04 '20 at 15:03