Модульный тест LiveData не проходит под сопрограммой и многопоточностью из-за возврата true вместо ожидаемого false - PullRequest
0 голосов
/ 04 ноября 2019

EnrollmentViewModelTest

@Test
fun getUserProfile() {
    val responseSnapshot = this.javaClass.getResource("/userDetailResponse.json").readText()
    val user = Gson().fromJson<User>(responseSnapshot, User::class.java)
    val response = Response.success(user)
    val deferred = CompletableDeferred<Response<User>>(response)

    coEvery { userService.getUserDetail() } returns deferred

    viewModel.getUserProfile()

    assert(viewModel.loadingStatus.value != null)
    assert(!UITestUtil.getValue(viewModel.loadingStatus)!!)
    assertEquals(false, viewModel.loadingStatus.value!!)
}

Вот EnrollmentViewModel.kt

fun getUserProfile() {
    loadingStatus.postValue(true)
    job = launch {
        callAsync {
            userService.getUserDetail()
        }.onSuccess { user ->
            if (user != null) {
                processUserDetails(user)
            }
            loadingStatus.postValue(false)
        }.onError {
            //
        }.onException {
            //
        }
    }
}

Когда я отлаживаю тестовый пример, он показывает, что UITestUtil.getValue (viewModel.loadingStatus) !! ложно.

Но странно, контрольный пример не проходит, и когда я печатаю UITestUtil.getValue (viewModel.loadingStatus) !!. Это правда.

Это может быть связано с loadingStatus.postValue(true)

После того, как я удалю его, результат печати будет ложным.

Но я не знаю почему.

object UITestUtil {
    /**
     * Gets the value of a LiveData safely.
     */
    @Throws(InterruptedException::class)
    fun <T> getValue(liveData: LiveData<T>): T? {
        var data: T? = null
        val latch = CountDownLatch(1)
        val observer = object : Observer<T> {
            override fun onChanged(o: T?) {
                data = o
                latch.countDown()
                liveData.removeObserver(this)
            }
        }
        liveData.observeForever(observer)
        latch.await(2, TimeUnit.SECONDS)

        return data
    }

}

Обновлено:

import com.google.gson.Gson
import kotlinx.coroutines.Deferred
import retrofit2.Response
import retrofit2.Response.success

data class VPlusResult<T : Any?>(
        val response: Response<T>? = null,
        val exception: Exception? = null
)

inline fun <T : Any> VPlusResult<T>.onSuccess(action: (T?) -> Unit): VPlusResult<T> {
    if (response?.isSuccessful == true)
        action(response.body())

    return this
}

inline fun <T : Any> VPlusResult<T>.onRawSuccess(action: (response: Response<T>) -> Unit): VPlusResult<T> {
    if (response?.isSuccessful == true)
        action(response)

    return this
}

inline fun <T : Any, TR : Any> VPlusResult<T>.map(action: (T?) -> (TR)) =
        if (response?.isSuccessful == true) VPlusResult(success(action(response.body())))
        else this as VPlusResult<TR>

inline fun <T : Any> VPlusResult<T>.onError(action: (String) -> Unit): VPlusResult<T> {
    if (response?.isSuccessful != true) {
        response?.errorBody()?.let {
            action(it.string())
        }
    }

    return this
}

inline fun <T : Any, reified G : Any> VPlusResult<T>.onErrorJson(action: (G) -> Unit): VPlusResult<T> {
    if (response?.isSuccessful != true) {
        response?.errorBody()?.let {
            action(Gson().fromJson(it.string(), G::class.java))
        }
    }

    return this
}

inline fun <T : Any> VPlusResult<T>.onRawError(action: (Response<T>?) -> Unit): VPlusResult<T> {
    if (response?.isSuccessful != true) {
        action(response)
    }

    return this
}

inline fun <T : Any?> VPlusResult<T>.onException(action: (Exception) -> Unit) {
    exception?.let { action(it) }
}

inline fun <T : Any> VPlusResult<T>.onSadness(action: (String?) -> Unit): VPlusResult<T> {
    onError {
        action(it)
    }.onException {
        action(it.message)
    }

    return this
}

suspend fun <T : Any> callAsync(block: () -> Deferred<Response<T>>): VPlusResult<T> {
    return try {
        VPlusResult(block().await())
    } catch (e: Exception) {
        VPlusResult(exception = e)
    }
}

Обновлено 2:

добавление любого из следующих операторов до того, как оператор assert получитзначение false, которое проходит тестовый пример.

coVerify { userService.getUserDetail() }
coVerify { viewModel.processUserDetails(user) }

Обновление 3: fun loadAllTasksFromRepository_loadingTogglesAndDataLoaded () в https://github.com/android/architecture-samples Также работает, но я пытаюсь ввести в свои кодытоже не получилось.

    @ExperimentalCoroutinesApi
    @Test
    fun getUserProfile4Using() {
        // using TestCoroutineScope in kotlinx.coroutines.test
        // Pause dispatcher so we can verify initial values
        mainCoroutineRule.pauseDispatcher()

        val responseSnapshot = this.javaClass.getResource("/userDetailResponse.json").readText()
        val user = Gson().fromJson<User>(responseSnapshot, User::class.java)
        val response = Response.success(user)
        val deferred = CompletableDeferred<Response<User>>(response)

//        coEvery { userService.getUserDetail() } returns deferred

        viewModel.getUserProfile()

        assert(viewModel.loadingStatus.value != null)

//        verify { viewModel.processUserDetails(user) }
        print(viewModel.loadingStatus.value)
        assert(UITestUtil.getValue(viewModel.loadingStatus)!!)
        assertEquals(true, viewModel.loadingStatus.value!!)


        // Execute pending coroutines actions
        mainCoroutineRule.resumeDispatcher()

        print(viewModel.loadingStatus.value!!)
        assert(!UITestUtil.getValue(viewModel.loadingStatus)!!)
        assertEquals(false, viewModel.loadingStatus.value!!)
    }

1 Ответ

0 голосов
/ 04 ноября 2019

Я бы сказал, что вам, вероятно, следует переписать свой тест, используя runBlockingTest (https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/)

fun getUserProfile() = runBlockingTest{
    val responseSnapshot = this.javaClass.getResource("/userDetailResponse.json").readText()
    val user = Gson().fromJson<User>(responseSnapshot, User::class.java)
    val response = Response.success(user)
    val deferred = CompletableDeferred<Response<User>>(response)

    coEvery { userService.getUserDetail() } returns deferred

    viewModel.getUserProfile()

    assert(viewModel.loadingStatus.value != null)
    assert(!UITestUtil.getValue(viewModel.loadingStatus)!!)
    assertEquals(false, viewModel.loadingStatus.value!!)
}

. Вам не придется использовать эти обратные отсчеты и т. Д. Этот подход буквально устранил все мои проблемыс тестированием сопрограмм, надеюсь, это поможет и вам :)! Если нет, просто дайте мне знать.

...