Модульные тесты ViewModel терпят неудачу при совместном запуске, но проходят при индивидуальном запуске

Я тестирую приостановленный метод из моего ViewModel, который запускает LiveData для создания объекта после завершения сопрограммы. Когда я запускаю каждый из этих тестов по отдельности, они проходят, когда я запускаю их вместе, всегда первый тест терпит неудачу. Удивительно, но когда я запускаю их в отладке и ставлю точки останова на assertValue, чтобы проверить, что такое vaule, оба теста проходят. Я предполагаю, что проблема связана с состоянием 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)))

person Booyaches    schedule 27.02.2020    source источник


Ответы (1)


Это может быть не полный ответ, потому что в вашем вопросе так много всего. Начните с попытки использовать 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.

person Rui    schedule 10.03.2020