在init块中使用postValue时,LiveData单元测试错误

时间:2018-10-11 14:18:05

标签: android kotlin android-architecture-components android-livedata android-viewmodel

我正在尝试使用实时数据为视图模型编写单元测试。

LoginViewModel.kt

class LoginViewModel @Inject constructor(
    val context: Context
): ViewModel() {
    val username = MutableLiveData<String>()
    val password = MutableLiveData<String>()
    val isLoginButtonEnabled = MediatorLiveData<Boolean>().apply {
        fun combineLatest(): Boolean {
            return !(username.value.isNullOrEmpty() || password.value.isNullOrEmpty())
        }
        addSource(username) { this.value = combineLatest() }
        addSource(password) { this.value = combineLatest() }
    }

    init {
        username.postValue("test")
        password.postValue("test")
    }
}

LoginViewModelTest.kt

@RunWith(MockitoJUnitRunner::class)
class LoginViewModelTest {
    @Rule
    @JvmField
    val instantTaskExecutorRole = InstantTaskExecutorRule()

    private val context = mock(Context::class.java)
    private val loginViewModel = LoginViewModel(context)

    @Test
    fun loginButtonDisabledOnEmptyUsername() {
        val observer = mock<Observer<Boolean>>()
        loginViewModel.isLoginButtonEnabled.observeForever(observer)
        loginViewModel.username.postValue("")

        verify(observer).onChanged(false)
    }
}

我的单元测试在username.postValue("test")行抛出以下异常:

java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.

在使用实时数据时,InstantTaskExecutorRule应该提供执行上下文,但是在init块中初始化实时数据时,它不起作用。当省略init块时,它可以按预期工作,但是我需要初始化实时数据变量的可能性。

在单元测试视图模型时,有什么方法可以使实时数据初始化工作?

3 个答案:

答案 0 :(得分:2)

我设法使用提到的规则-ViewModel对使用LiveData的{​​{1}}进行单元测试。但就我而言,规则val声明有些不同:

InstantTaskExecutorRule

编辑:

@Suppress("unused")
@get:Rule
val instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()

Edit2:

出于某些奇怪的原因,我无法重现此内容:) 另外,我认为问题可能是由于您初始化ViewModel的方式-

@Before
@Throws(Exception::class)
fun prepare() {
    MockitoAnnotations.initMocks(this)
}

我认为它初始化得太早了,因此它的init块也被太早调用了。也许用private val loginViewModel = LoginViewModel(context) 方法创建它是合理的吗?喜欢:

@Before

答案 1 :(得分:2)

LiveData的{​​{1}}期间设置ViewModel值时,我看到了类似的问题。 Demigod的解决方案为我指明了正确的方向,但是我想解释一下测试过程中发生了什么以及为什么在生命周期中的原因。

当您有initViewModel期间设置LiveData时,它将在视图模型初始化后立即运行。当您使用init在单元测试中初始化视图模型时,将在初始化测试类的同时实例化该视图模型。问题是您可能同时初始化了任何规则,但实际上没有 run ,直到类完全初始化后才开始,因此您的val viewModel = MyViewModel()发生在规则实际采用之前影响。这意味着您的实时数据不能在即时执行器上运行,任何Rx可观察对象都不能在替换的调度程序上运行,等等。因此,最终有两种解决方法:

  1. 将视图模型定义为ViewModel.init(),并在测试的lateinit var方法中将视图模型初始化为@Before,该方法将在应用规则后运行,或者
  2. 将视图模型定义为val viewModel by lazy { MyViewModel() },直到您在测试中实际开始调用它时,该模型才会运行。

我更喜欢选项2,因为它还允许我在初始化视图模型之前设置任何特定于测试用例的先决条件,并且不必在每个内部都进行重复的初始化代码(可能非常冗长)。进行测试。

答案 2 :(得分:0)

我遇到了类似的问题,半神提供的答案没有解决。我终于找到了魔鬼的藏身之处,所以我在这里分享:我的 init 块是在 liveData 初始化之前设置的,在运行应用程序时可以正常工作,但在运行测试时却没有!

GroupBy