我对Kotlin Android还是个新手.我正在try 学习测试,但收到一个错误,显示从未设置实时数据值,即使我使用AdvanceUntilIdle也是如此.

这是我的储存库

class UserRepository private constructor(
    private val userPreference: UserPreference,
    private val apiService: ApiService
) {
  
    private val _loginResponse = MutableLiveData<LoginResponse?>()
    val loginResponse: LiveData<LoginResponse?> = _loginResponse

    private val _errorLoginMessage = MutableLiveData<String?>()
    val errorLoginMessage: LiveData<String?> = _errorLoginMessage

    fun login(email: String, password: String) {
        _showLoading.value = true
        val client = apiService.login(email, password)

        client.enqueue(object : Callback<LoginResponse> {
            override fun onResponse(
                call: Call<LoginResponse>, response: Response<LoginResponse>
            ) {
                _showLoading.value = false
                Log.d(TAG, "Register Response : ${response.body()}")
                if (response.isSuccessful && response.body() != null) {
                    _loginResponse.value = response.body()
                } else {
                    _errorLoginMessage.value = response.message()
                    Log.e(TAG, "onFailure: ${response.message()}")
                }
            }

            override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
                _showLoading.value = false
                _errorLoginMessage.value = t.message
                Log.e(TAG, "onFailure: ${t.message}")
            }
        })
    }
}

这是我的取景模型

class LoginViewModel(private val repository: UserRepository) : ViewModel() {

    val loginResponse: LiveData<LoginResponse?> = repository.loginResponse
    val errorMessage = repository.errorLoginMessage

    fun login(email: String, password: String) {
        repository.login(email, password)
    }
}

这是我的测试

@RunWith(RobolectricTestRunner::class)
class LoginViewModelTest {

    private lateinit var viewModel: LoginViewModel
    private val context = RuntimeEnvironment.getApplication().applicationContext

    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setup() {
        val userRepository = UserRepository.getInstance(
            UserPreference.getInstance(context.dataStore)
        )

        viewModel = LoginViewModel(
            userRepository
        )

        Dispatchers.setMain(StandardTestDispatcher())
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }

    @Test
    fun `Test Login Failed`() = runTest {
        viewModel.login("testWrongEmail@gmail.com", "testWrongPassword")
        advanceUntilIdle()
        val result = viewModel.loginResponse.getOrAwaitValue()
        Assert.assertEquals(true, result?.error)
        Assert.assertEquals("failed", result?.message)
    }
}

它在运行时运行得很好,但在测试时就不行了.为什么会这样呢?

我正在try 添加InstantRuleExecutor,设置主调度程序,并添加runTest.我也试着把排队改为执行,但仍然没有结果.

推荐答案

advanceUntilIdle执行挂起的coroutines.这一点从它是kotlinx-coroutines-test一揽子计划的一部分这一事实中得到了暗示.

viewModel.login("testWrongEmail@gmail.com", "testWrongPassword")呼叫ViewModel.login()呼叫UserRepository.login(),后者呼叫client.enqueue().client.enqueue()是一个基于回调的异步函数,它使用线程(一种更传统的并发性方式,早于协程).这意味着client.enqueue()不会触发协程程序.

这意味着advanceUntilIdle()调用将在client.enqueue()完成之前返回,这会导致您遇到问题.

修复方法是将ViewModel.login()实现改为使用协程.

最简单的方法是使用Call.execute()而不是enqueue()将网络调用转换为同步调用.然后,您可以使用runInterruptible将同步调用转换为可取消的协程调用.

把所有这些放在一起,


    fun login(email: String, password: String) {
        _showLoading.value = true

        // launch a new coroutine
        viewModelScope.launch {
            try {
                // Use the IO dispatcher since this is a network call
                val response = runInterruptible(Dispatchers.IO) {
                    apiService.login(email, password).execute()
                }

                _showLoading.value = false
                Log.d(TAG, "Register Response : ${response.body()}")
                if (response.isSuccessful && response.body() != null) {
                    _loginResponse.value = response.body()
                } else {
                    _errorLoginMessage.value = response.message()
                    Log.e(TAG, "onFailure: ${response.message()}")
                }
            } catch (e: Exception) {
                _showLoading.value = false
                _errorLoginMessage.value = e.message
                Log.e(TAG, "onFailure: ${e.message}")
            }
        }
    }
}

Android相关问答推荐

无法在Android Gradle中同步Chaquopy版本

用于小部件泄漏警告的伙伴对象中的Kotlin Lateinit

StateFlow和LazyColumn重组

Jetpack Compose:带芯片的Textfield

在Jetpack Compose中将导航绘图显示在顶部栏下方、底部栏上方

Jetpack Compose X.dp 性能问题?

在 Bash 脚本中使用 XMLLINT 解析 XML 单元测试文件,并将其放入数组中以表示成功和失败

当父布局的背景为浅色时,Android ProgressBar 背景 colored颜色 变为灰色

通知使用默认语言,屏幕显示多种语言,同时通过 AppCompatDelegate 设置应用程序语言

setContentView() 方法的签名

[Android][Room] 将密封类存储到 Room 数据库中

系统导航栏在某些场景下应用了深色效果

如何在 JetpackCompose 的 LazyColumn 中 Select 多个项目

在事件中使用 Context/Toast 时不需要的重组 - Jetpack Compose

如何使用 Jetpack Compose 制作两个圆圈

如何在 Android 应用程序未激活/未聚焦时显示视图?

Android Studio xml 预览问题无法初始化编辑器

如何将设备屏幕位置转换为发送事件位置?

如何在jetpack compose中创建自定义rememberState?

构成material 3 中的分隔符