Spring Boot集成测试中如何Mock外部REST服务?
如何Mock Spring Integration Http Outbound Gateway调用的外部REST服务
你的Mock未生效的核心原因是:Spring Integration的Http Outbound Gateway默认会自行创建RestTemplate实例,而你在测试中new的RestTemplate和实际Flow使用的不是同一个对象,MockRestServiceServer绑定的是自己创建的实例,自然无法拦截实际请求。
以下是几种可行的解决方案:
方案一:自定义RestTemplate Bean并绑定Mock
步骤1:修改Spring Integration配置,使用自定义RestTemplate
在SpringIntegrationConfiguration中定义RestTemplate Bean,并指定Http Outbound Gateway使用该实例:
@Bean public RestTemplate integrationRestTemplate() { return new RestTemplate(); } public IntegrationFlow callingAExternalService() { return flow -> flow.handle( Http.outboundGateway(externalServiceURL) .httpMethod(HttpMethod.POST) .expectedResponseType(String.class) .restTemplate(integrationRestTemplate()) // 绑定自定义RestTemplate ) .logAndReply("Cd response"); }
步骤2:在测试类中注入并Mock该RestTemplate
@Tag("integrationtest") @ExtendWith(SpringExtension.class) @SpringBootTest( classes = MyWebApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class IntegrationTest { @LocalServerPort private int port; @Autowired private RestTemplate integrationRestTemplate; private MockRestServiceServer mockServer; TestRestTemplate testRestTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); @BeforeEach void setUp() throws JsonProcessingException { mockServer = MockRestServiceServer.bindTo(integrationRestTemplate).build(); DecisionResponse decisionResponse = new DecisionResponse(); decisionResponse.setId("0013478"); decisionResponse.setResponse(null); decisionResponse.setDescription("dummy Response"); mockServer .expect(requestTo("http://xyz-some-url:8080/some-other-service/v1/do-decisions/decision")) .andExpect(method(HttpMethod.POST)) .andRespond( withStatus(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON) .body(new ObjectMapper().writeValueAsString(decisionResponse))); } @Test void testEntireApplication() { HttpEntity<LoanProvisionRequest> entity = new HttpEntity<>(TestHelper.generateValidLionRequest(), headers); ResponseEntity<LoanProvisionResponse> response = testRestTemplate.exchange( createURLWithPort("/provision"), HttpMethod.POST, entity, LionResponse.class); assertEquals(1, response.getBody().getASMCreditScoreResultCd()); mockServer.verify(); // 测试完成后验证请求是否被Mock处理 } private String createURLWithPort(String uri) { return "http://localhost:" + port + "/lion-service/v1/decisions" + uri; } }
注意:不要在
setUp中提前调用mockServer.verify(),否则会在测试执行前验证请求,导致失败。
方案二:使用@MockBean直接替换RestTemplate(更简洁)
无需修改原配置类,直接在测试中用@MockBean注入RestTemplate,Spring会自动将该Mock实例注入到Http Outbound Gateway中:
@Tag("integrationtest") @ExtendWith(SpringExtension.class) @SpringBootTest( classes = MyWebApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class IntegrationTest { @LocalServerPort private int port; @MockBean private RestTemplate restTemplate; private MockRestServiceServer mockServer; TestRestTemplate testRestTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); @BeforeEach void setUp() throws JsonProcessingException { mockServer = MockRestServiceServer.bindTo(restTemplate).build(); DecisionResponse decisionResponse = new DecisionResponse(); decisionResponse.setId("0013478"); decisionResponse.setResponse(null); decisionResponse.setDescription("dummy Response"); mockServer .expect(requestTo("http://xyz-some-url:8080/some-other-service/v1/do-decisions/decision")) .andExpect(method(HttpMethod.POST)) .andRespond( withStatus(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON) .body(new ObjectMapper().writeValueAsString(decisionResponse))); } @Test void testEntireApplication() { HttpEntity<LoanProvisionRequest> entity = new HttpEntity<>(TestHelper.generateValidLionRequest(), headers); ResponseEntity<LoanProvisionResponse> response = testRestTemplate.exchange( createURLWithPort("/provision"), HttpMethod.POST, entity, LionResponse.class); assertEquals(1, response.getBody().getASMCreditScoreResultCd()); mockServer.verify(); } private String createURLWithPort(String uri) { return "http://localhost:" + port + "/lion-service/v1/decisions" + uri; } }
方案三:使用WireMock模拟完整HTTP服务(复杂场景适用)
如果外部服务调用逻辑复杂,或需要模拟多种响应场景,可以用WireMock启动临时HTTP服务器:
步骤1:添加WireMock依赖
<dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock-jre8</artifactId> <version>2.35.0</version> <scope>test</scope> </dependency>
步骤2:测试类中配置WireMock
@Tag("integrationtest") @ExtendWith(SpringExtension.class) @SpringBootTest( classes = MyWebApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class IntegrationTest { @LocalServerPort private int port; private WireMockServer wireMockServer; TestRestTemplate testRestTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); @BeforeEach void setUp() throws JsonProcessingException { wireMockServer = new WireMockServer(WireMockConfiguration.options().dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); DecisionResponse decisionResponse = new DecisionResponse(); decisionResponse.setId("0013478"); decisionResponse.setResponse(null); decisionResponse.setDescription("dummy Response"); stubFor(post(urlEqualTo("/some-other-service/v1/do-decisions/decision")) .willReturn(aResponse() .withStatus(HttpStatus.OK.value()) .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .withBody(new ObjectMapper().writeValueAsString(decisionResponse)))); } @DynamicPropertySource static void registerDynamicProperties(DynamicPropertyRegistry registry) { // 覆盖外部服务地址为WireMock服务器地址 registry.add("external.service.url", () -> "http://localhost:" + wireMockServer.port()); } @AfterEach void tearDown() { wireMockServer.stop(); } @Test void testEntireApplication() { HttpEntity<LoanProvisionRequest> entity = new HttpEntity<>(TestHelper.generateValidLionRequest(), headers); ResponseEntity<LoanProvisionResponse> response = testRestTemplate.exchange( createURLWithPort("/provision"), HttpMethod.POST, entity, LionResponse.class); assertEquals(1, response.getBody().getASMCreditScoreResultCd()); verify(postRequestedFor(urlEqualTo("/some-other-service/v1/do-decisions/decision"))); } private String createURLWithPort(String uri) { return "http://localhost:" + port + "/lion-service/v1/decisions" + uri; } }
这种方式完全模拟真实HTTP服务,不依赖Spring RestTemplate机制,适合复杂集成测试场景。
内容的提问来源于stack exchange,提问作者Somnath Mukherjee
相关产品推荐
相关产品推荐

