测试ViewModel中MutableState更新时断言失败,该如何解决?
问题描述
我编写了如下的PhotoViewModel:
class PhotoViewModel( private val getPhotoUseCase: GetPhotoUseCase ) : ViewModel() { val photo: MutableState<PhotoItem?> = mutableStateOf(null) fun getPhotoDetail(photoId: Int) { viewModelScope.launch { val photo = getPhotoUseCase(photoId) photoDetail.value = photo } } }
尝试通过以下代码测试状态是否正确更新:
@Test fun `state is updated correctly`() = runTest { coEvery { getPhotoDetail(id = 0) } returns PhotoItem( ... ) systemUnderTest.getPhotoDetail(id = 0) val actualState = systemUnderTest.photoDetail.value assertEquals(mockedPhotoItemUiModel, actualState) }
测试失败,报错信息显示断言时比较的是对象内存引用:
Expected :com.package.PhotoItem@3791f50e Actual :com.package.PhotoItem@574b7f4a
请问是否应该使用其他断言方法?当前测试MutableState的方式是否完全错误?找不到测试MutableState的官方资料,仅找到一篇英文Stack Overflow帖子。
解决方案
1. 核心问题:对象相等性判断失败
测试失败的根本原因不是MutableState的测试方式错误,而是PhotoItem没有正确实现值相等性。默认情况下,Kotlin类的equals方法比较对象引用,因此即使两个PhotoItem属性完全一致,只要是不同实例,assertEquals就会失败。
2. 修复对象相等性
给PhotoItem添加值相等的实现,有两种常用方式:
- 将
PhotoItem定义为data class:Kotlin的data class会自动生成基于属性的equals、hashCode和toString方法,直接解决值相等问题:data class PhotoItem( val id: Int, val url: String, // 其他属性 ) - 若无法改为data class,手动重写
equals和hashCode方法:class PhotoItem( val id: Int, val url: String ) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as PhotoItem if (id != other.id) return false if (url != other.url) return false return true } override fun hashCode(): Int { var result = id result = 31 * result + url.hashCode() return result } }
3. MutableState的测试方式无需推翻
你的测试思路是正确的:
- 使用
runTest处理viewModelScope的协程逻辑是标准做法 - 调用方法后直接获取
photoDetail.value的方式可行,runTest会等待协程执行完成
若担心异步更新的不确定性,可添加advanceUntilIdle()确保所有协程执行完毕:
@Test fun `state is updated correctly`() = runTest { val mockedPhoto = PhotoItem(id = 0, url = "test-url") coEvery { getPhotoUseCase(id = 0) } returns mockedPhoto systemUnderTest.getPhotoDetail(0) advanceUntilIdle() // 等待协程完成状态更新 assertEquals(mockedPhoto, systemUnderTest.photoDetail.value) }
4. 可选的其他断言方法
若不想修改PhotoItem的equals实现,可使用断言库的属性匹配功能,比如AssertJ:
import org.assertj.core.api.Assertions.assertThat @Test fun `state is updated correctly`() = runTest { val mockedPhoto = PhotoItem(id = 0, url = "test-url") coEvery { getPhotoUseCase(id = 0) } returns mockedPhoto systemUnderTest.getPhotoDetail(0) advanceUntilIdle() val actual = systemUnderTest.photoDetail.value assertThat(actual) .isNotNull .hasFieldOrPropertyWithValue("id", 0) .hasFieldOrPropertyWithValue("url", "test-url") }
内容的提问来源于stack exchange,提问作者Gianluca Veschi
相关产品推荐
相关产品推荐

