繁体   English   中英

ViewModel 单元测试一起运行时失败,但单独运行时通过

[英]ViewModel unit tests fail when run together but pass when run individually

我正在从我的ViewModel测试一个挂起的方法,该方法在协程完成时触发LiveData发出一个对象。 当我单独运行每个测试时,它们通过,当我一起运行它们时,第一个测试总是失败。 令人惊讶的是,当我在调试中运行它们并在assertValue处放置断点以检查值是什么时,两个测试都通过了。 我的猜测是问题出在LiveData或整个PaymentViewModel 我究竟做错了什么?

class PaymentViewModelTest : KoinTest {
private val paymentViewModel : PaymentViewModel by inject()

@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()

private val mainThreadSurrogate = newSingleThreadContext("UI thread")

@Before
fun setup(){
    Dispatchers.setMain(mainThreadSurrogate)
    val modules = KoinModule()
    startKoin {
        androidContext(mock(Application::class.java))
        modules(listOf(
            modules.repositoryModule,
            modules.businessModule,
            modules.utilsModule)
        )
    }
    declareMock<AnalyticsHelper>()
    declareMock<Printer>()
}

@After
fun after(){
    stopKoin()
    Dispatchers.resetMain()
}

@Test
fun successfully_initializes_payment_flow() {
    declareMock<PaymentRepository> {
        runBlockingTest {
            given(initPayment())
                .willAnswer { InitPaymentResponse(0, PaymentStatus.INITIALIZED, 0) }
        }
    }
    paymentViewModel.initPayment(BigDecimal(0))
    paymentViewModel.paymentStatus.test()
        .awaitValue()
        .assertValue { value -> value.getContentIfNotHandled()?.data == PaymentStatus.INITIALIZED }
}

@Test
fun fails_to_initialize_payment_flow() {
    declareMock<PaymentRepository> {
        runBlockingTest {
            given(initPayment())
                .willThrow(MockitoKotlinException("", ConnectException()))
        }
    }
    paymentViewModel.initPayment(BigDecimal(0))
    paymentViewModel.paymentStatus.test()
        .awaitValue()
        .assertValue { value -> value.getContentIfNotHandled()?.status == ApiResponseStatus.ERROR}
}  
}

这是我正在测试的方法:

fun initPayment(price: BigDecimal) {
    paymentStatus.postValue(Event(ApiResponse.loading()))
    viewModelScope.launch {
        runCatching {
            repository.initPayment()
        }.onSuccess {
            paymentSession = PaymentSession(it.paymentId)
            paymentSession.price = price
            postPaymentStatus(it.status)
        }.onFailure {
            postApiError(it)
        }
    }
}

private fun postPaymentStatus(status: PaymentStatus) =
    paymentStatus.postValue(Event(ApiResponse.success(status)))

这可能不是一个完整的答案,因为您的问题太多了。 首先尝试使用 CoroutineTestRule:

@ExperimentalCoroutinesApi
class CoroutineTestRule(
    private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : TestWatcher() {

    override fun starting(description: Description?) {
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description?) {
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }
}

您的测试将类似于:

class PaymentViewModelTest : KoinTest {
    private val paymentViewModel : PaymentViewModel by inject()

    @get:Rule
    val coroutineTestRule = CoroutineTestRule()

    @Before
    fun setup(){
        startKoin {
            androidContext(mock(Application::class.java))
            modules(
                modules.repositoryModule,
                modules.businessModule,
                modules.utilsModule
            )
        }
        declareMock<AnalyticsHelper>()
        declareMock<Printer>()
    }

    @After
    fun after(){
        stopKoin()
    }

    // Other methods are the same.
}

您可以使用 AutoCloseKoinTest 删除 after() 方法。

你说当你孤立地运行它时测试通过了,所以也许这已经足够了。 但如果这不起作用,还有更多的东西需要挖掘。 例如,我觉得奇怪的是您在模拟中使用 runBlockingTest 而断言在该块之外。 通常我会使用 MockK 来模拟挂起函数并在 runBlockingTest 中测试和断言它们中的任何一个。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM