使用Hilt和Mockk测试ViewModel时出现assertEquals匹配错误
我用Kotlin、AndroidX、Jetpack Compose开发,Hilt做依赖注入,Mockk做Mock,在对MainMenuViewModel做单元测试时遇到两个问题:
- 测试用例
testMainMenuViewModel执行失败,错误信息:
expected:<[image1.jpg, image2.jpg]> but was:<[]>
Expected :[image1.jpg, image2.jpg]
Actual :[]
错误出现在assertEquals(galleryList, viewModel.photoGalleryState.value)行。ViewModel的photoGalleryState.value在init块中通过galleryRepository.getImageGalleryList()赋值,我已Mock了galleryRepository并通过every指定返回值,但结果仍为空列表。当前Hilt版本为2.46.1(不支持@HiltViewModelTest),Mockk版本1.13.5。
- 若添加
verify { viewModel.photoGalleryState.value },会抛出错误:
Missing calls inside verify { ... } block.
io.mockk.MockKException: Missing calls inside verify { ... } block.
相关代码
ViewModel类
import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.xgame.xgarage.model.MenuItem import com.xgame.xgarage.model.MenuItemRepository import com.xgame.xgarage.model.MenuPhotoGalleryRepository import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @HiltViewModel class MainMenuViewModel @Inject constructor( galleryRepository: MenuPhotoGalleryRepository, menuItemRepository: MenuItemRepository ) : ViewModel() { val photoGalleryState: MutableState<List<String>> = mutableStateOf(emptyList()) val itemMenuState: MutableState<List<MenuItem>> = mutableStateOf(emptyList()) init { photoGalleryState.value = galleryRepository.getImageGalleryList() itemMenuState.value = menuItemRepository.getMenuItemsList() } }
单元测试代码
import com.xgames.xgarage.model.MenuItem import com.xgames.xgarage.model.MenuItemRepository import com.xgames.xgarage.model.MenuPhotoGalleryRepository import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.RelaxedMockK import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test class MainMenuViewModelTest { @RelaxedMockK private lateinit var galleryRepository: MenuPhotoGalleryRepository @RelaxedMockK private lateinit var menuItemRepository: MenuItemRepository private lateinit var viewModel: MainMenuViewModel @Before fun setup() { MockKAnnotations.init(this) viewModel = MainMenuViewModel(galleryRepository, menuItemRepository) } @Test fun testMainMenuViewModel() { // Prepare mock data val galleryList = listOf("image1.jpg", "image2.jpg") val menuList = listOf(MenuItem("file://some/file/name1.jpg", "1"), MenuItem("file://some/file/name2.jpg", "2")) // Mock repository methods every { galleryRepository.getImageGalleryList() } returns galleryList every { menuItemRepository.getMenuItemsList() } returns menuList // Verify the initial state of the view model assertEquals(galleryList, viewModel.photoGalleryState.value) } }
MenuPhotoGalleryRepository类
class MenuPhotoGalleryRepository { private val imageGalleryX = listOf( "file:///android_asset/images/cars/x1.jpg", "file:///android_asset/images/cars/x2.jpg", "file:///android_asset/images/cars/x3.jpg" ) fun getImageGalleryList(): List<String> { return imageGalleryX } }
MenuItem类
data class MenuItem(val imagePath: String, val menuName: String)
ViewModel在Compose中的使用
composable(route = "main_menu_screen") { val viewModel: MainMenuViewModel = hiltViewModel() MainMenuScreen(viewModel.photoGalleryState.value, viewModel.itemMenuState.value) }
问题原因与解决办法
1. Mock返回值不生效的问题
核心原因:初始化顺序错误。你在@Before方法中先创建了ViewModel,之后才在测试方法里设置Mock的返回值。但ViewModel的init块在实例创建时就会执行,此时Mock还未配置返回值,所以galleryRepository.getImageGalleryList()返回的是RelaxedMock默认的空列表。
解决办法:将配置Mock返回值的代码移到创建ViewModel之前,修改后的测试类如下:
class MainMenuViewModelTest { @RelaxedMockK private lateinit var galleryRepository: MenuPhotoGalleryRepository @RelaxedMockK private lateinit var menuItemRepository: MenuItemRepository private lateinit var viewModel: MainMenuViewModel private val galleryList = listOf("image1.jpg", "image2.jpg") private val menuList = listOf(MenuItem("file://some/file/name1.jpg", "1"), MenuItem("file://some/file/name2.jpg", "2")) @Before fun setup() { MockKAnnotations.init(this) // 先配置Mock返回值 every { galleryRepository.getImageGalleryList() } returns galleryList every { menuItemRepository.getMenuItemsList() } returns menuList // 再创建ViewModel,此时init块会使用已配置的Mock返回值 viewModel = MainMenuViewModel(galleryRepository, menuItemRepository) } @Test fun testMainMenuViewModel() { // 直接验证状态即可 assertEquals(galleryList, viewModel.photoGalleryState.value) assertEquals(menuList, viewModel.itemMenuState.value) } }
2. Verify语句报错的问题
verify { ... }是用来验证Mock对象的方法调用,而viewModel.photoGalleryState.value是访问ViewModel的属性,不属于Mock对象的方法调用,因此会报错。
如果需要验证Repository的方法是否被调用,可以这样写:
verify { galleryRepository.getImageGalleryList() }
若只是确认ViewModel的状态值,直接使用assertEquals即可,无需verify。
内容的提问来源于stack exchange,提问作者greysqrl

