【问题标题】:Unit Test : Observer onChanged should be called twice instead of once单元测试:观察者 onChanged 应该被调用两次而不是一次
【发布时间】:2019-04-26 20:56:34
【问题描述】:

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

我有两个测试。当我单独启动每个测试时没问题,但是当我连续启动所有测试时出现错误。 每次我从 API。我希望android.arch.lifecycle.Observer.onChanged 被调用两次,但第二次测试只调用一次。 当我在第一次测试中将 verify(view, times(2)).onChanged(arg.capture()) 替换为 verify(view, atLeastOnce()).onChanged(arg.capture()) 时,单元测试工作正常。

用户视图模型:

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

用户视图模型测试:

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

【问题讨论】:

  • 你的IDE中UserViewModelTest.kt的第70行代码是什么?既然有错误的原因。
  • 那是我用Error ocurred here 评论的地方。我将编辑代码以使其准确。
  • 你终于解决了这个问题吗?
  • @Miguel:看我的回答

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


【解决方案1】:

我遇到了这个确切的问题。我将测试方式更改为以下(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))
        }
    }
}

【讨论】:

  • 你是最棒的!它对我有用,但这都是关于超时的,因为你可以设置延迟(100),然后它不会退出测试,直到它被调用,虽然片状,但没关系。非常感谢!
  • 不明白为什么,但captureValues 方法只返回最后一次调用postValue。例如,如果我有 3 次实时数据调用,postValue, captureValues` 方法将只有最后一个。 LiveDataValueCapture 内部将仅保存变量 _values 中的最后一个值
  • @IgoriS 那是因为这是postValue 的工作方式,当你调用它两次时,在分派值时,它只会设置传递给它的最后一个值。 “如果您在主线程执行发布的任务之前多次调用此方法,则只会分派最后一个值。”参考:developer.android.com/reference/android/arch/lifecycle/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多