Kotlin Spring Boot测试接口Mock Repository层失败,求正确方法
解决Spring Boot + Kotlin中@WebMvcTest测试时的InjectMocksException问题
这个问题我之前也碰到过,核心原因是你混用了Mockito的@InjectMocks和Spring Boot测试的依赖管理机制,导致UserService的构造参数(非空的userRepository)无法正确注入。咱们一步步来修正它:
错误根源分析
你看到的InjectMocksException是因为:
@WebMvcTest注解只会自动加载Web层的Bean(也就是你的UserEndpoints控制器),不会加载Service和Repository层的Bean。- 你用
@InjectMocks标记userService,并调用initMocks(this),但Mockito的InjectMocks无法感知Spring通过@MockBean创建的userRepository实例,尝试实例化UserService时,非空的构造参数userRepository为null,直接触发Kotlin的非空断言异常。
正确的Mock Repository测试方式
我们应该让Spring Boot测试框架来处理依赖注入,而不是手动用Mockito的初始化逻辑。具体步骤如下:
1. 调整测试类注解
- 保留
@RunWith(SpringRunner::class)和@WebMvcTest(UserEndpoints::class),这两个是Web层测试的核心注解。 - 添加
@Import(UserService::class):因为@WebMvcTest默认不加载Service层,我们需要手动导入UserService,让Spring创建它的实例。 - 用
@MockBean标记userRepository:Spring会自动创建这个Mock实例,并注入到UserService中。
2. 移除冗余代码
- 删除
@InjectMocks注解和setup方法里的initMocks(this)调用,这些是导致冲突的原因。 - 把可空类型的变量改成Kotlin的
lateinit,避免代码里到处都是!!强制非空断言,更符合Kotlin的编码习惯。
修正后的完整测试代码
@RunWith(SpringRunner::class) @WebMvcTest(UserEndpoints::class) @Import(UserService::class) // 导入Service,让Spring管理其实例 class UserEndpointsTest { @Autowired private lateinit var mockMvc: MockMvc @MockBean private lateinit var userRepository: UserRepository @Test fun testGetUser() { // 准备测试数据并Mock Repository的行为 val targetUser = User("2", "username1", "password1", "1234", 101, UserType.USER) Mockito.`when`(userRepository.findById(eq("2"))) .thenReturn(Optional.of(targetUser)) // 执行请求并验证响应 mockMvc.perform(get("/user").param("id", "2")) .andExpect(status().isOk) .andExpect(jsonPath("$.username").value("username1")) .andExpect(jsonPath("$.remaining_requests").value(101)) .andExpect(jsonPath("$.type").value("USER")) } // 测试数据生成方法可以保留 private fun users(): List<User> { val user1 = User("1", "username", "password", "123", 100, UserType.BETA) val user2 = User("2", "username1", "password1", "1234", 101, UserType.USER) return listOf(user1, user2) } }
额外优化建议
- 使用mockito-kotlin简化代码:添加
mockito-kotlin依赖后,你可以用更简洁的whenever(userRepository.findById("2"))代替Mockito.when(...),代码可读性更好。 - 避免自定义any方法:
mockito-kotlin已经提供了类型安全的any()扩展函数,不需要自己实现。 - 处理多依赖场景:如果
UserService还有其他依赖,要么用@MockBeanMock掉,要么用@Import导入对应的真实Bean(如果需要测试真实逻辑的话)。
内容的提问来源于stack exchange,提问作者0x3d
相关产品推荐
相关产品推荐

