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

Android ViewModel单元测试在一起运行时失败,但在单独运行时通过,android,unit-testing,kotlin-coroutines,koin,Android,Unit Testing,Kotlin Coroutines,Koin,我正在从我的ViewModel测试一个挂起的方法,该方法在协同路由完成时触发LiveData以发出一个对象。什么时候 我分别运行通过的每个测试,当我一起运行它们时,第一个测试总是失败。令人惊讶的是,当我在debug中运行它们并将断点放在assertValue以检查变量是什么时,两个测试都通过了。我猜问题在于LiveData或整个PaymentViewModel的状态。我做错了什么 class PaymentViewModelTest : KoinTest { private val paymen

我正在从我的
ViewModel
测试一个挂起的方法,该方法在协同路由完成时触发
LiveData
以发出一个对象。什么时候 我分别运行通过的每个测试,当我一起运行它们时,第一个测试总是失败。令人惊讶的是,当我在debug中运行它们并将断点放在
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}
}  
}

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

@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.
}
class PaymentViewModelTest:KoinTest{
私有val paymentViewModel:paymentViewModel by inject()
@获取:规则
val coroutinestrule=coroutinestrule()
@以前
趣味设置(){
斯塔特科因{
androidContext(mock(Application::class.java))
模块(
modules.repositoryModule,
模块,业务模块,
modules.utilsModule
)
}
declareMock()
declareMock()
}
@之后
之后的乐趣{
stopKoin()
}
//其他方法也一样。
}
可以使用AutoCloseKoinTest删除after()方法


当您单独运行测试时,您会说测试通过了,所以这可能就足够了。但如果这不起作用,还有更多的问题需要研究。例如,我觉得奇怪的是,在模拟中使用runBlockingTest,而断言在该块之外。通常我会使用mock来模拟挂起函数,并在runBlockingTest中测试和断言它们中的任何一个。

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

@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.
}
class PaymentViewModelTest:KoinTest{
私有val paymentViewModel:paymentViewModel by inject()
@获取:规则
val coroutinestrule=coroutinestrule()
@以前
趣味设置(){
斯塔特科因{
androidContext(mock(Application::class.java))
模块(
modules.repositoryModule,
模块,业务模块,
modules.utilsModule
)
}
declareMock()
declareMock()
}
@之后
之后的乐趣{
stopKoin()
}
//其他方法也一样。
}
可以使用AutoCloseKoinTest删除after()方法

当您单独运行测试时,您会说测试通过了,所以这可能就足够了。但如果这不起作用,还有更多的问题需要研究。例如,我觉得奇怪的是,在模拟中使用runBlockingTest,而断言在该块之外。通常我会使用mock来模拟挂起函数,并在runBlockingTest中测试和断言它们中的任何一个