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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 06:35:37