You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Hilt和Mockk测试ViewModel时出现assertEquals匹配错误

ViewModel单元测试Mockk返回值不生效问题(Kotlin+Hilt+Jetpack Compose)

我用Kotlin、AndroidX、Jetpack Compose开发,Hilt做依赖注入,Mockk做Mock,在对MainMenuViewModel做单元测试时遇到两个问题:

  1. 测试用例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。

  1. 若添加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)
    }
}
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
    }
}
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 05:03:17