【问题标题】:Why do my Android unit tests fail when run together, but pass when run individually?为什么我的 Android 单元测试一起运行时失败,但单独运行时通过?
【发布时间】:2018-10-18 20:21:24
【问题描述】:

这是我的整个测试课:

@RunWith(JUnit4::class)
class ExplorerRemoteImplTest {

    // Mocks
    private val mockDatabase = mock<FirebaseFirestore>(
            defaultAnswer = RETURNS_DEEP_STUBS
    )
    private val mockQuerySnapshot = mock<QuerySnapshot>()
    private val mockQuerySnapshotTask = mock<Task<QuerySnapshot>>()

    // Class under test
    private lateinit var explorerRemoteImpl: ExplorerRemoteImpl

    // Others
    private val poiList = listOf(TestDataFactory.makePoiRepo(),TestDataFactory.makePoiRepo())

    @Before
    fun setup(){
        //create instance of class under test
        explorerRemoteImpl = ExplorerRemoteImpl(mockDatabase)


        // Step #1 return the query Task on get().
        whenever(mockDatabase.collection(ArgumentMatchers.anyString()).orderBy(ArgumentMatchers.anyString()).get()).thenReturn(mockQuerySnapshotTask)

        // Step #2 return a queryTask when registering the listener
        whenever(mockQuerySnapshotTask.addOnCompleteListener(anyOrNull()))
                .thenReturn(mockQuerySnapshotTask)

        // Step #3 task IS successful is stubbed

        // Step #4 the results of the task is a QuerySnapshot
        whenever(mockQuerySnapshotTask.result).thenReturn(mockQuerySnapshot)

        // Step #5 QuerySnapshot = is empty or not is stubbed

        // Step #6 when we try to convert snapShot to objects
        whenever(mockQuerySnapshot.toObjects(PoiRepository::class.java)).thenReturn(poiList)
    }

    private fun stubQuerySnapshotIsEmpty(boolean: Boolean){
        whenever(mockQuerySnapshot.isEmpty).thenReturn(boolean)
    }

    private fun stubQueryTaskIsSuccessful(boolean: Boolean){
        whenever(mockQuerySnapshotTask.isSuccessful).thenReturn(boolean)
    }

    @After
    fun onEnd(){
        Mockito.reset(mockQuerySnapshotTask)
        Mockito.reset(mockDatabase)
        Mockito.reset(mockQuerySnapshot)
    }

    @Test
    fun getPoisCompletes() {

        // GIVEN
        stubQueryTaskIsSuccessful(true)
        stubQuerySnapshotIsEmpty(false)

        val testObserver = explorerRemoteImpl.getPois().test()

        // Trigger callback reply
        // see: https://fernandocejas.com/2014/04/08/unit-testing-asynchronous-methods-with-mockito/
        val captor = argumentCaptor<OnCompleteListener<QuerySnapshot>>()
        verify(mockQuerySnapshotTask).addOnCompleteListener(captor.capture())
        captor.lastValue.onComplete(mockQuerySnapshotTask)

        verify(mockQuerySnapshotTask, times(1)).addOnCompleteListener(anyOrNull())

        // THEN
        testObserver
                .assertNoErrors()
                .assertValueCount(1)
                .assertComplete()
    }

    @Test
    fun getPoisCompletesOnEmptyQuerySnapshot() {

        // GIVEN
        stubQueryTaskIsSuccessful(true)
        stubQuerySnapshotIsEmpty(true)

        val testObserver = explorerRemoteImpl.getPois().test()

        // Trigger callback reply
        // see: https://fernandocejas.com/2014/04/08/unit-testing-asynchronous-methods-with-mockito/
        val captor = argumentCaptor<OnCompleteListener<QuerySnapshot>>()
        verify(mockQuerySnapshotTask).addOnCompleteListener(captor.capture())
        captor.firstValue.onComplete(mockQuerySnapshotTask)

        // THEN
        testObserver
                .assertNoErrors()
                .assertValueCount(0)
                .assertComplete()

        Mockito.verify(mockQuerySnapshotTask, times(1)).addOnCompleteListener(anyOrNull())
    }

    @Test
    fun getPoisErrorsOnNoSuccessQuerySnapshot() {

        // GIVEN
        stubQueryTaskIsSuccessful(false)
        stubQuerySnapshotIsEmpty(true)

        val testObserver = explorerRemoteImpl.getPois().test()

        // Trigger callback reply
        val captor = argumentCaptor<OnCompleteListener<QuerySnapshot>>()
        verify(mockQuerySnapshotTask).addOnCompleteListener(captor.capture())
        captor.firstValue.onComplete(mockQuerySnapshotTask)

        Mockito.verify(mockQuerySnapshotTask, times(1)).addOnCompleteListener(anyOrNull())

        // task Exception not mocked, so unknown is passed via Elvis operator
        testObserver.assertError(UnknownError::class.java)
    }
}

有 3 个单元测试,它们在单独运行时都通过了,但是当我运行整个测试类时,我的第 2 和第 3 测试失败,并显示如下错误:

Wanted but not invoked:
task.addOnCompleteListener(
    <Capturing argument>
);
-> at com.loc8r.seattleexplorer.remote.ExplorerRemoteImplTest.getPoisCompletesOnEmptyQuerySnapshot(ExplorerRemoteImplTest.kt:113)
Actually, there were zero interactions with this mock.

我已经尝试了我能想到的一切来解决这个问题:

  1. 我将我的待测类实例化移动到 @Before 函数中。
  2. 我尝试创建一个 @After 函数并在我的模拟上调用 Mockito.reset。

我应该提到我正在使用 nhaarman.mockitokotlin2 库,它是 argumentCaptor。

关于为什么这些测试单独运行时通过但作为一个类一起运行时失败的任何线索?

【问题讨论】:

  • 我之前也遇到过这种情况。检查您是否有可以在测试中更改的全局变量。或许您可以在@Before@After 中重置它们。

标签: android mockito


【解决方案1】:

您是否尝试过避免使用在您的情况下为 ExplorerRemoteImpl 的被测类的单个共享实例?尝试为每个测试方法创建一个新实例。

实际上我面临着与您在上面描述的完全相同的问题,但我无法重新实例化我的被测类,因为我尝试测试一个单例。

更新: 我已经重构了我的单例的实现,所以为了测试我可以为每个测试方法实例化被测类。该问题不再出现。

【讨论】:

    猜你喜欢
    • 2012-02-17
    • 2021-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    相关资源
    最近更新 更多