Kotlin SpringBoot测试中@MockBean Mock对象随机返回null问题
Kotlin结合Mockito开展SpringBoot测试时Mock随机失效问题
问题根因
- Mock桩配置与生命周期不匹配:
@MockBean注入的Mock对象,Spring Boot Test默认会在每个测试方法执行完成后自动清空所有桩配置。现有代码将Mock规则写在@BeforeAll标记的方法中,该方法仅在测试类初始化时执行一次,桩规则仅对第一个测试方法生效。第一个测试执行完成后Mock规则被清空,后续测试请求、测试收尾阶段未断开的SocketIO客户端发出的心跳/重连请求,调用authApi.authorize()时无匹配桩规则,Mockito会返回对象类型默认值null。 - Kotlin空安全与Mockito默认行为兼容漏洞:业务代码中
authApi.authorize()声明返回非空Authorization类型,但Mockito基于Java字节码生成代理类,无法感知Kotlin的非空约束,无匹配桩时直接返回null。现有代码在runCatching块中未将authorize()返回值赋值给显式声明的非空变量,Kotlin未在调用点插入运行时空检查,导致返回的null未触发异常进入onFailure分支,反而被判定为执行成功传入onSuccess回调,最终在AuthorizationContext.set(it)行触发空指针。 - 异步请求时序不确定:SocketIO连接、心跳、事件交互均为异步逻辑,
@BeforeAll中启动客户端连接后,请求可能在测试生命周期任意节点发出,包括测试方法间隙Mock被重置的窗口、测试上下文销毁阶段,进一步放大了Mock失效概率,表现为随机报错。
修复方案
- 调整Mock桩配置时机:将Mock规则配置逻辑从
@BeforeAll移到@BeforeEach方法中,每个测试方法执行前重新打桩,保证测试运行时桩规则始终有效;同时将参数匹配器从anyOrNull()替换为anyString(),匹配业务逻辑中token不可能为null的前置判断,减少匹配误判。
对应配置代码示例:
@BeforeEach fun setupMock() { Mockito.`when`(mockedApiComponent.authorize(anyString())).thenReturn(auth) }
- 补全Kotlin运行时空检查:在
runCatching块中显式将授权结果赋值给非空类型变量,触发Kotlin内置的运行时空校验,一旦Mock返回null直接抛出异常进入onFailure分支,避免空值流入后续逻辑。
修正后的过滤器代码片段:
runCatching { // 显式声明非空类型,触发Kotlin运行时空检查 val authResult: Authorization = authApi.authorize(token.replace("Bearer ", "")) authResult }.onSuccess { AuthorizationContext.set(it) filterChain.doFilter(request, response) }.onFailure { it.printStackTrace() response.sendError(401) }
- 对齐异步组件生命周期:新增
@AfterAll方法,测试执行完成后主动断开SocketIO客户端、停止SocketIO服务,避免测试结束后残留的异步请求调用已被重置的Mock对象。
对应销毁代码示例:
@AfterAll fun cleanup() { clientSocket.close() socketIOServer.stop() }
- 可选优化:引入mockito-kotlin库适配Kotlin空安全特性,或开启Mockito严格桩模式,测试过程中一旦出现未配置桩的方法调用直接抛出错误,提前暴露问题而非静默返回null。
内容的提问来源于stack exchange,提问作者Rafael Lima
相关产品推荐
相关产品推荐

