ViewModel Unit тестирует несколько состояний просмотра с помощью LiveData, Coroutines и MockK

У меня есть функция в ViewModel с двумя 9X_android-sdk состояниями: первое состояние всегда ЗАГРУЖАЕТСЯ, второе 9X_android-ui состояние зависит от результата взаимодействия 9X_android api или db.

Это функция

fun getPostWithSuspend() { myCoroutineScope.launch { // Set current state to LOADING _postStateWithSuspend.value = ViewState(LOADING) val result = postsUseCase.getPosts() // Check and assign result to UI val resultViewState = if (result.status == SUCCESS) { ViewState(SUCCESS, data = result.data?.get(0)?.title) } else { ViewState(ERROR, error = result.error) } _postStateWithSuspend.value = resultViewState } } 

И никаких ошибок, тест 9X_android отлично работает для проверки окончательного 9X_kotlin-coroutines результата ERROR или SUCCESS

 @Test fun `Given DataResult Error returned from useCase, should result error`() = testCoroutineRule.runBlockingTest { // GIVEN coEvery { useCase.getPosts() } returns DataResult.Error(Exception("Network error occurred.")) // WHEN viewModel.getPostWithSuspend() // THEN val expected = viewModel.postStateWithSuspend.getOrAwaitMultipleValues(dataCount = 2) // Truth.assertThat("Network error occurred.").isEqualTo(expected?.error?.message) // Truth.assertThat(expected?.error).isInstanceOf(Exception::class.java) coVerify(atMost = 1) { useCase.getPosts() } } 

Но я не смог 9X_android-sdk найти способ проверить, произошло ли состояние 9X_kotlinx.coroutines LOADING или нет, поэтому я изменил существующую 9X_kotlinx.coroutines функцию расширения на

fun LiveData.getOrAwaitMultipleValues( time: Long = 2, dataCount: Int = 1, timeUnit: TimeUnit = TimeUnit.SECONDS, afterObserve: () -> Unit = {} ): List { val data = mutableListOf() val latch = CountDownLatch(dataCount) val observer = object : Observer { override fun onChanged(o: T?) { data.add(o) latch.countDown() [email protected](this) } } this.observeForever(observer) afterObserve.invoke() // Don't wait indefinitely if the LiveData is not set. if (!latch.await(time, timeUnit)) { this.removeObserver(observer) throw TimeoutException("LiveData value was never set.") } @Suppress("UNCHECKED_CAST") return data.toList() } 

Для добавления данных 9X_android-livedata в список при изменении LiveData и сохранения 9X_unittest состояний в этом списке, но он никогда не 9X_android возвращает состояние LOADING, потому что 9X_android-ui это происходит до начала наблюдения. Есть 9X_android ли способ проверить несколько значений LiveData?

11
0
5
Общее количество ответов: 5

Ответ #1

Ответ на вопрос: ViewModel Unit тестирует несколько состояний просмотра с помощью LiveData, Coroutines и MockK

Я предполагаю, что вы используете библиотеку 9X_android-sdk mockk

  1. Сначала вам нужно создать объект-наблюдатель

     val observer = mockk>> { every { onChanged(any()) } just Runs } 
  2. Наблюдайте 9X_android-livedata за своими живыми данными, используя предыдущий 9X_android-ui объект наблюдателя

    viewModel.postStateWithSuspend.observeForever(observer) 
  3. Вызовите функцию getPostWithSuspend()

    viewModel.getPostWithSuspend() 
  4. Проверить

     verifySequence { observer.onChanged(yourExpectedValue1) observer.onChanged(yourExpectedValue2) } 

12
2

  • Да, это похоже на лучшее решение, но, по крайней мере, у меня проблема с проверками в блоке verifySequence. Они должны быть точно такими же, как и экземпляры, опубликованные в LiveData. Кто-нибудь знает, как решить эту пробл ...

Ответ #2

Ответ на вопрос: ViewModel Unit тестирует несколько состояний просмотра с помощью LiveData, Coroutines и MockK

Используя mockk, вы можете фиксировать значения 9X_unit-testing и сохранять их в списке, а затем проверять 9X_android-livedata значения по порядку.

 //create mockk object val observer = mockk>() //create slot val slot = slot() //create list to store values val list = arrayListOf() //start observing viewModel.postStateWithSuspend.observeForever(observer) //capture value on every call every { observer.onChanged(capture(slot)) } answers { //store captured value list.add(slot.captured) } viewModel.getPostWithSuspend() //assert your values here 

12
0

Ответ #3

Ответ на вопрос: ViewModel Unit тестирует несколько состояний просмотра с помощью LiveData, Coroutines и MockK

  • Наблюдает за [LiveData] и фиксирует последнее значение и все последующие значения, возвращая их в упорядоченном списке.
  • inline fun LiveData.captureValues(): List { val mockObserver = mockk>() val list = mutableListOf() every { mockObserver.onChanged(captureNullable(list))} just runs this.observeForever(mockObserver) return list } 

    9X_unittest

4
0

Ответ #4

Ответ на вопрос: ViewModel Unit тестирует несколько состояний просмотра с помощью LiveData, Coroutines и MockK

Здравствуйте, вы можете просто решить эту 9X_android-sdk проблему, немного изменив расширение. Это 9X_unit-testing как:

Главное, что вам нужно сначала увеличить 9X_kotlinx.coroutines количество защелок, а затем не удалять наблюдателя, пока 9X_android обратный отсчет защелки не станет равен 9X_livedata нулю.

Остальное будет легко, вам просто нужно 9X_android-livedata сохранить результат в списке.

Удачи!

 /* Add for multiple values in LiveData */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) fun LiveData.getOrAwaitValuesTest( time: Long = 2, timeUnit: TimeUnit = TimeUnit.SECONDS, maxCountDown: Int = 1, afterObserve: () -> Unit = {} ): List { val data: MutableList = mutableListOf() val latch = CountDownLatch(maxCountDown) val observer = object : Observer { override fun onChanged(o: T?) { data.add(o) latch.countDown() if (latch.count == 0L) { [email protected](this) } } } this.observeForever(observer) try { afterObserve.invoke() // Don't wait indefinitely if the LiveData is not set. if (!latch.await(time, timeUnit)) { throw TimeoutException("LiveData value was never set.") } } finally { this.removeObserver(observer) } return data.toList() } 

2
0

Ответ #5

Ответ на вопрос: ViewModel Unit тестирует несколько состояний просмотра с помощью LiveData, Coroutines и MockK

Я написал свой собственный обозреватель 9X_unit-testing тестов в стиле RxJava для LiveData

class LiveDataTestObserver constructor( private val liveData: LiveData ) : Observer { init { liveData.observeForever(this) } private val testValues = mutableListOf() override fun onChanged(t: T) { if (t != null) testValues.add(t) } fun assertNoValues(): LiveDataTestObserver { if (testValues.isNotEmpty()) throw AssertionError( "Assertion error with actual size ${testValues.size}" ) return this } fun assertValueCount(count: Int): LiveDataTestObserver { if (count < 0) throw AssertionError( "Assertion error! value count cannot be smaller than zero" ) if (count != testValues.size) throw AssertionError( "Assertion error! with expected $count while actual ${testValues.size}" ) return this } fun assertValue(vararg predicates: T): LiveDataTestObserver { if (!testValues.containsAll(predicates.asList())) throw AssertionError("Assertion error!") return this } fun assertValue(predicate: (List) -> Boolean): LiveDataTestObserver { predicate(testValues) return this } fun values(predicate: (List) -> Unit): LiveDataTestObserver { predicate(testValues) return this } fun values(): List { return testValues } /** * Removes this observer from the [LiveData] which was observing */ fun dispose() { liveData.removeObserver(this) } /** * Clears data available in this observer and removes this observer from the [LiveData] which was observing */ fun clear() { testValues.clear() dispose() } } fun LiveData.test(): LiveDataTestObserver { val testObserver = LiveDataTestObserver(this) // Remove this testObserver that is added in init block of TestObserver, and clears previous data testObserver.clear() observeForever(testObserver) return testObserver } 

И использовать 9X_android-ui его как

 val testObserver = viewModel. postStateWithSuspend.test() // WHEN viewModel. getPostWithSuspend() // THEN testObserver .assertValue { states -> ( states[0].status == Status.LOADING && states[1].status == Status.ERROR ) } 

1
0