如何为Spring Integration DSL配置、网关、控制器编写JUnit5测试用例
Spring Integration Scatter-Gather 流JUnit测试实现
先提前修正原代码里会导致测试启动失败的问题:
- 配置类里注入的是
LoansServiceImpl loansService,但三个子流逻辑里调用的是lionService,需要把注入实例改成实际使用的LionService类型,不然启动会报找不到Bean的错误。 - 主流程里的
.convert(LoanProvisionRequest.class)和后面子流接收的LionRequest类型不匹配,按实际入参类型调整,不然会抛类型转换异常。 - 配置类里直接初始化
dbId的写法有问题,所有请求会共用同一个ID,把ID生成逻辑移到flow1的处理方法里,按请求维度单独生成。
前置依赖
如果是Spring Boot项目,引入测试starter即可,不需要额外加其他依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-test</artifactId> <scope>test</scope> </dependency>
1. 集成流+网关层测试
这层测试加载完整Spring集成上下文,Mock掉业务服务依赖,直接验证Scatter-Gather的并行调用、结果聚合逻辑,不需要启动Web容器:
@SpringBootTest @SpringIntegrationTest class LionFlowTest { @Autowired private LionGateway lionGateway; @MockBean private LionService lionService; @Test void testAllSubFlowExecutedAndResultAggregated() throws JsonProcessingException { // 构造测试入参 LionRequest req = new LionRequest(); req.setBizId("test-biz-001"); req.setContent("test payload"); // 模拟业务方法返回 doNothing().when(lionService).saveLionRequest(any(LionRequest.class), anyString()); when(lionService.getData(any(LionRequest.class), eq(SourceSystem.PROVISION))).thenReturn("flow2-return"); when(lionService.prepareCDRequest(any(Message.class))).thenReturn("flow3-return"); // 触发流执行 List<?> aggregateResult = lionGateway.echo(req); // 断言:3个recipient子流的消息全部被收集 assertEquals(3, aggregateResult.size()); // 验证三个子流的业务方法都被调用过一次 verify(lionService, times(1)).saveLionRequest(any(), anyString()); verify(lionService, times(1)).getData(any(), eq(SourceSystem.PROVISION)); verify(lionService, times(1)).prepareCDRequest(any()); } @Test void testSubFlowThrowException() throws JsonProcessingException { LionRequest req = new LionRequest(); doThrow(new JsonProcessingException("json parse fail"){}).when(lionService).saveLionRequest(any(), anyString()); // 验证子流异常能正常抛出,不会挂住线程 assertThrows(RuntimeException.class, () -> lionGateway.echo(req)); } }
2. 控制器层测试
这层测试只加载Web层上下文,Mock掉网关依赖,验证接口的序列化、响应逻辑,不需要启动完整集成流,执行速度更快:
@WebMvcTest(LionController.class) class LionControllerTest { @Autowired private MockMvc mockMvc; @MockBean private LionGateway lionGateway; @Autowired private ObjectMapper objectMapper; @Test void testInvokeEndpointReturnCorrectResponse() throws Exception { LionRequest req = new LionRequest(); req.setBizId("ctrl-test-001"); String reqJson = objectMapper.writeValueAsString(req); // 模拟网关返回聚合结果 List<String> mockReturn = Arrays.asList("flow1-done", "flow2-done", "flow3-done"); when(lionGateway.echo(any(LionRequest.class))).thenReturn(mockReturn); // 发请求验证 mockMvc.perform(post("/invoke-integration") .contentType(MediaType.APPLICATION_JSON) .content(reqJson)) .andExpect(status().isOk()) .andExpect(content().string(mockReturn.toString())); verify(lionGateway, times(1)).echo(any(LionRequest.class)); } }
测试调优提示
- 因为流里用了异步线程池,测试如果出现偶发断言失败,不要硬写
Thread.sleep,引入Awaitility包做异步结果等待即可;调试阶段可以把所有异步通道临时改成同步通道,排查完逻辑再改回异步配置。 - 如果需要验证Scatter-Gather自带的序列号、关联ID等消息头,可以直接注入
output-flow通道,拉取完整消息对象做断言,不需要只依赖网关返回的payload。 - 测试环境可以自定义线程池Bean,把核心线程数、最大线程数调小,避免测试时创建过多线程占用资源。
内容的提问来源于stack exchange,提问作者Somnath Mukherjee
相关产品推荐
相关产品推荐

