android - 单元测试: Observer onChanged should be called twice instead of once

标签 android unit-testing mockito android-viewmodel rx-kotlin

为什么在对 ViewModel 进行单元测试时会得到不同的结果?

我进行了两项测试。当我单独启动每个测试时,这没问题,但是当我连续启动所有测试时,我收到了错误。 这是一个 ViewModel,每次我从一个返回时都会改变状态 API。我希望 android.arch.lifecycle.Observer.onChanged 被调用两次,但第二次测试只调用一次。 当我将 verify(view, times(2)).onChanged(arg.capture()) 替换为 verify(view, atLeastOnce()).onChanged(arg.capture) 时,单元测试工作正常()) 在第一次测试时。

用户 View 模型:

class UserViewModel(
        private val leApi: LeApi
): ViewModel() {
    private val _states = MutableLiveData<ViewModelState>()
    val states: LiveData<ViewModelState>
        get() = _states

    fun getCurrentUser() {
        _states.value = LoadingState
        leApi.getCurrentUser()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        { user -> _states.value = UserConnected(user) },
                        { t -> _states.value = FailedState(t) }
                )
        }
    }
}

用户 View 模型测试:

@RunWith(MockitoJUnitRunner::class)
class UserViewModelTest {

    lateinit var userViewModel: UserViewModel

    @Mock
    lateinit var view: Observer<ViewModelState>

    @Mock
    lateinit var leApi: LeApi

    @get:Rule
    val rule = InstantTaskExecutorRule()

    @Before
    fun setUp() {
        RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
        userViewModel = UserViewModel(leApi)
        userViewModel.states.observeForever(view)
    }

    @Test
    fun testGetCurrentUser() {
        val user = Mockito.mock(User::class.java)
        `when`(leApi.getCurrentUser()).thenReturn(Single.just(user))
        userViewModel.getCurrentUser()

        val arg = ArgumentCaptor.forClass(ViewModelState::class.java)
        verify(view, times(2)).onChanged(arg.capture())

        val values = arg.allValues

        assertEquals(2, values.size)
        assertEquals(LoadingState, values[0])
        assertEquals(UserConnected(user), values[1])
    }

    @Test
    fun testGetCurrentUserFailed() {
        val error = Throwable("Got error")
        `when`(leApi.getCurrentUser()).thenReturn(Single.error(error))
        userViewModel.getCurrentUser()

        val arg = ArgumentCaptor.forClass(ViewModelState::class.java)
        verify(view, times(2)).onChanged(arg.capture()) // Error occurred here. That's the 70th line from stack trace.

        val values = arg.allValues
        assertEquals(2, values.size)
        assertEquals(LoadingState, values[0])
        assertEquals(FailedState(error), values[1])
    }
}

预计: 所有测试均已通过。

实际:

org.mockito.exceptions.verification.TooLittleActualInvocations: 
view.onChanged(<Capturing argument>);
Wanted 2 times:
-> at com.dev.titi.toto.mvvm.UserViewModelTest.testGetCurrentUserFailed(UserViewModelTest.kt:70)
But was 1 time:
-> at android.arch.lifecycle.LiveData.considerNotify(LiveData.java:109)

最佳答案

我遇到了这个问题。我将测试方式更改为以下( Google recommendations, here are the classes used for following test ):

将协程添加到您的项目中,因为测试助手使用它们:

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1")
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.0'

去掉这一行:

lateinit var view: Observer<ViewModelState>

然后将您的测试更改为以下内容:

private val testDispatcher = TestCoroutineDispatcher()

@Before
fun setup() {
    Dispatchers.setMain(testDispatcher)
    ...
}

@After
fun tearDown() {
    Dispatchers.resetMain()
    testDispatcher.cleanupTestCoroutines()
    ...
}

@Test
fun testGetCurrentUser() {
    runBlocking {
        val user = Mockito.mock(User::class.java)
        `when`(leApi.getCurrentUser()).thenReturn(Single.just(user))
        userViewModel.states.captureValues {
            userViewModel.getCurrentUser()
            assertSendsValues(100, LoadingState, UserConnected(user))
        }
    }
}

关于android - 单元测试: Observer onChanged should be called twice instead of once,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55874840/

相关文章:

Android - 使用动画的神器

android - 广播组 : Horizontal layout and Span multiple lines

用于 getter setter 的 Java lombok 单元测试

java - PowerMockito:java.lang.NoClassDefFoundError

java - 当我将多个自定义匹配器分配给一个方法时,Mockito 的行为很奇怪

c# - 系统.IO.DirectoryNotFoundException : Could not find a part of the path ( android build in unity )

android - 使用从右到左语言的 EditText numperPassword

java - Gradle不运行测试

android - 使用 Mockito 模拟 PreferenceManager

java - 为什么使用 RealmResults 时我的 Robolectric 测试失败?