匹配错误(assertEquals)在使用Hilt和Mockk进行单元测试ViewModel时发生。

huangapple go评论55阅读模式
英文:

Matching Error (assertEquals) when unit testing ViewModel with Hilt and Mockk

问题

我正在尝试对我的ViewModel进行单元测试。我的代码是使用Kotlin、Android-X和Jetpack Compose编写的,我正在使用Hilt进行依赖注入,使用Mockk进行模拟。

我遇到的问题是,当我运行测试testMainMenuViewModel时,我收到以下错误:

expected:<[image1.jpg, image2.jpg]> but was:<[]>
Expected :[image1.jpg, image2.jpg]
Actual   :[]

这与这一行相关:

assertEquals(galleryList, viewModel.photoGalleryState.value)

photoGalleryState.valueMainMenuViewModel 中赋值,即 photoGalleryState.value = galleryRepository.getImageGalleryList(),而 galleryRepository 被模拟,并且每次调用 getImageGalleryList 都已处理。

我不清楚我是否做错了什么,例如,我评估的方式是否正确,或者是否因为我使用了Hilt而需要采取不同的方法。

我使用的是 Hilt 2.46.1(不支持 HilViewModelTest)和 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()
    }
}

我的 ViewModel 单元测试:

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() {
        // 准备模拟数据
        val galleryList = listOf("image1.jpg", "image2.jpg")
        val menuList = listOf(MenuItem("file://some/file/name1.jpg", "1"), MenuItem("file://some/file/name2.jpg", "2"))

        // 模拟仓库方法
        every { galleryRepository.getImageGalleryList() } returns galleryList
        every { menuItemRepository.getMenuItemsList() } returns menuList

        // 验证视图模型的初始状态
        assertEquals(galleryList, viewModel.photoGalleryState.value)
        //assertEquals(menuList, viewModel.itemMenuState.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)

在我的 MainActivity 类中使用 ViewModel(在可组合中使用):

composable(route = "main_menu_screen") {
    val viewModel: MainMenuViewModel = hiltViewModel()
    MainMenuScreen(viewModel.photoGalleryState.value, viewModel.itemMenuState.value)
}

希望这可以帮助您解决问题。

英文:

I am trying to unit test my ViewModel. My code is written using Kotlin, Android-X and Jetpack compose and I am using Hilt for Dependency Injection and Mockk for... Mocking

The issue I'm having is that when I run the test testMainMenuViewModel I receive the following error:

expected:&lt;[image1.jpg, image2.jpg]&gt; but was:&lt;[]&gt;
Expected :[image1.jpg, image2.jpg]
Actual   :[]

that relates to this line:

assertEquals(galleryList, viewModel.photoGalleryState.value)

photoGalleryState.value is assigned in the MainMenuViewModel, i.e.
photoGalleryState.value = galleryRepository.getImageGalleryList()

and galleryRepository is mocked and 'every' call to getImageGalleryList is handled.

I'm not clear if I'm doing something wrong, i.e. the way I'm evaluating, or if I need to do something different because I am using Hilt.

I am using Hilt 2.46.1 (which doesn't support HilViewModelTest) and Mockk 1.13.5

In addition, if I add in the line
verify { viewModel.photoGalleryState.value }

I receive the error

Missing calls inside verify { ... } block.
io.mockk.MockKException: Missing calls inside verify { ... } block.

My ViewModel class.

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&lt;List&lt;String&gt;&gt; = mutableStateOf(emptyList())
    val itemMenuState: MutableState&lt;List&lt;MenuItem&gt;&gt; = mutableStateOf(emptyList())

    init {
        photoGalleryState.value = galleryRepository.getImageGalleryList()
        itemMenuState.value = menuItemRepository.getMenuItemsList()
    }
}

My ViewModel Unit Test

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)
        //galleryRepository = mockk&lt;MenuPhotoGalleryRepository&gt;()
        //menuItemRepository = mockk&lt;MenuItemRepository&gt;()
        viewModel = MainMenuViewModel(galleryRepository, menuItemRepository)
    }

    @Test
    fun testMainMenuViewModel() {
        // Prepare mock data
        val galleryList = listOf(&quot;image1.jpg&quot;, &quot;image2.jpg&quot;)
        val menuList = listOf(MenuItem(&quot;file://some/file/name1.jpg&quot;, &quot;1&quot;), MenuItem(&quot;file://some/file/name2.jpg&quot;, &quot;2&quot;))

        // Mock repository methods
        every { galleryRepository.getImageGalleryList() } returns galleryList
        every { menuItemRepository.getMenuItemsList() } returns menuList

        // Verify the initial state of the view model
        //viewModel.photoGalleryState.value
        assertEquals(galleryList, viewModel.photoGalleryState.value)
        //assertEquals(menuList, viewModel.itemMenuState.value)
    }
}

MenuPhotoGalleryRepository

class MenuPhotoGalleryRepository {
    private val imageGalleryX = listOf(
        &quot;file:///android_asset/images/cars/x1.jpg&quot;,
        &quot;file:///android_asset/images/cars/x2.jpg&quot;,
        &quot;file:///android_asset/images/cars/x3.jpg&quot;
    )

    fun getImageGalleryList(): List&lt;String&gt; {
        return imageGalleryX
    }
}

MenuItem class

data class MenuItem(val imagePath: String, val menuName: String)

Usage of the ViewModel within my MainActivity class (used within a composable)

composable(route = &quot;main_menu_screen&quot;) {
    val viewModel: MainMenuViewModel = hiltViewModel()
    MainMenuScreen(viewModel.photoGalleryState.value, viewModel.itemMenuState.value)
}

Any help or guidance is gratefully appreciated.

答案1

得分: 1

我认为你需要像这样更改测试代码:

@Test
fun testMainMenuViewModel() {
    // 准备模拟数据
    val galleryList = listOf("image1.jpg", "image2.jpg")
    val menuList = listOf(MenuItem("file://some/file/name1.jpg", "1"), MenuItem("file://some/file/name2.jpg", "2"))

    // 模拟存储库方法
    every { galleryRepository.getImageGalleryList() } returns galleryList
    every { menuItemRepository.getMenuItemsList() } returns menuList

    // 在定义模拟操作之后创建视图模型
    viewModel = MainMenuViewModel(galleryRepository, menuItemRepository)

    // 验证视图模型的初始状态
    //viewModel.photoGalleryState.value
    assertEquals(galleryList, viewModel.photoGalleryState.value)
    //assertEquals(menuList, viewModel.itemMenuState.value)
}

当你在设置函数中创建视图模型时,视图模型的初始化块将被调用。因此,模拟对象被松散化,它们返回空列表。我猜这就是发生的情况。

英文:

I think you need to change the test code like this

@Test
fun testMainMenuViewModel() {
    // Prepare mock data
    val galleryList = listOf(&quot;image1.jpg&quot;, &quot;image2.jpg&quot;)
    val menuList = listOf(MenuItem(&quot;file://some/file/name1.jpg&quot;, &quot;1&quot;), MenuItem(&quot;file://some/file/name2.jpg&quot;, &quot;2&quot;))

    // Mock repository methods
    every { galleryRepository.getImageGalleryList() } returns galleryList
    every { menuItemRepository.getMenuItemsList() } returns menuList

    // create view model after defining actions of mock
    viewModel = MainMenuViewModel(galleryRepository, menuItemRepository)

    // Verify the initial state of the view model
    //viewModel.photoGalleryState.value
    assertEquals(galleryList, viewModel.photoGalleryState.value)
    //assertEquals(menuList, viewModel.itemMenuState.value)
}

When you create a view model in setup function, the init block of the view model will be invoked. So, the mocks are relaxed, they return empty list. I guess that what happened.

huangapple
  • 本文由 发表于 2023年7月11日 01:53:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/76656185.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定