英文:
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.value
在 MainMenuViewModel
中赋值,即 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:<[image1.jpg, image2.jpg]> but was:<[]>
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<List<String>> = mutableStateOf(emptyList())
val itemMenuState: MutableState<List<MenuItem>> = 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<MenuPhotoGalleryRepository>()
//menuItemRepository = mockk<MenuItemRepository>()
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
//viewModel.photoGalleryState.value
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 class
data class MenuItem(val imagePath: String, val menuName: String)
Usage of the ViewModel within my MainActivity class (used within a composable)
composable(route = "main_menu_screen") {
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("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
// 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论