Модульное тестирование LiveData ObserverForever приводит к исключению NullPointer с Junit5 - PullRequest
0 голосов
/ 04 мая 2020

Я использую Android привязку данных для прослушивания изменений данных в реальном времени, и я хотел бы наблюдать за изменениями на уровне модели представления (вместо того, чтобы наблюдать за фрагментом и затем отправлять обратный вызов в модель представления). observerForever интересен тем, что служит цели для меня. Однако, когда я запускаю тест, я получаю следующую ошибку:

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)

Мой код выглядит следующим образом в классе viewmodel:

val observerEmail: Observer<String> = Observer {
    setEmailError(it)
    checkLoginButton()
}
var email = MutableLiveData<String>()
init {
    email.observeForever(observerEmail)
}

Также следует отметить, что я использую 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()
    )
}}

А это 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 Ответ

0 голосов
/ 04 мая 2020

Как правило, рекомендуется использовать LiveData только для представления модели <-> Просмотр связи, однако я думаю, что проблема заключается в следующем:

private val loginViewModel: LoginViewModel = LoginViewModel(
    ...
)

Поскольку, поскольку это переменная-член, она будет выполняться перед тестом. и он уже неявно выполняет init(), так как вы вызываете конструктор. Нет необходимости звонить init() явно. Я бы удалил переменную-член loginViewModel и создал бы ее в тестовой функции через конструктор:

@Test
fun should_return_user_as_null_initially() {
    ...
    LoginViewModel(
       ...
    )
    ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...