ngu*_*ngu 8 android kotlin android-livedata android-viewmodel android-architecture-components
我正在尝试使用实时数据为视图模型编写单元测试。
登录视图模型.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")
}
}
Run Code Online (Sandbox Code Playgroud)
登录ViewModelTest.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)
}
}
Run Code Online (Sandbox Code Playgroud)
我的单元测试在该行抛出以下异常username.postValue("test"):
java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.
Run Code Online (Sandbox Code Playgroud)
在InstantTaskExecutorRule应该使用实时数据时提供一个执行环境,但它初始化的实时数据时不工作init-块。省略init-block 时,它可以按需要工作,但我需要初始化实时数据变量的可能性。
在单元测试视图模型时,有什么方法可以使实时数据初始化工作?
LiveData在 期间设置值ViewModel时我遇到了类似的问题init。Demigod 的解决方案为我指明了正确的方向,但我想解释一下测试过程的生命周期中发生了什么以及为什么。
当您ViewModel设置了LiveDatawhile 时init,它将在视图模型初始化后立即运行。当您使用 初始化单元测试中的视图模型时val viewModel = MyViewModel(),该视图模型将在初始化测试类的同时实例化。问题是您可能同时初始化任何规则,但直到类完全初始化之后才实际运行ViewModel.init(),因此您的规则是在规则实际生效之前发生的。这意味着您的实时数据无法在即时执行器上运行,任何 Rx 可观察量都不会在替换的调度程序上运行,等等。因此最终有两种方法可以解决此问题:
lateinit var中将视图模型初始化为 a ,该方法在应用规则后运行,或者@Beforeval viewModel by lazy { MyViewModel() },直到您实际开始在测试中调用它为止,它才会运行。我更喜欢选项 2,因为它还允许我在初始化视图模型之前设置任何特定于测试用例的先决条件,并且我不必在每个需要的测试中执行重复的初始化代码(这可能非常冗长)它。
我设法使用提到的规则对我的ViewModel使用LiveData进行了单元测试- InstantTaskExecutorRule。但在我的情况下,规则 val 声明有点不同:
@Suppress("unused")
@get:Rule
val instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()
Run Code Online (Sandbox Code Playgroud)
编辑:
@Before
@Throws(Exception::class)
fun prepare() {
MockitoAnnotations.initMocks(this)
}
Run Code Online (Sandbox Code Playgroud)
编辑2:
出于某种奇怪的原因,我无法重现这个 :) 另外,我认为问题可能是因为您初始化 ViewModel 的方式 -
private val loginViewModel = LoginViewModel(context)
Run Code Online (Sandbox Code Playgroud)
我认为它初始化得太早,因此它的 init 块也太早被调用了。也许在@Before方法中创建它是合理的?喜欢:
private lateinit var viewModel: LoginViewModel
@Before
@Throws(Exception::class)
fun prepare() {
loginViewModel = LoginViewModel(context)
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4498 次 |
| 最近记录: |