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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:39:07