Модульные тесты ViewModel не выполняются при запуске вместе, но проходят при индивидуальном запуске - PullRequest
1 голос
/ 27 февраля 2020

Я тестирую приостановленный метод из моего 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)))

1 Ответ

0 голосов
/ 10 марта 2020

Возможно, это не полный ответ, потому что в вашем вопросе так много всего. Начните с попытки использовать 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 внутри макета, а assert находится за пределами этого блока. Обычно я использую MockK для имитации приостановленных функций, а также для проверки и утверждения любой из них внутри runBlockingTest.

...