使用 @WebServiceServerTest 进行 Spring Web Service 集成测试

1、简介

本文将带你了解如何使用 @WebServiceServerTest 为 Spring Boot 开发的 SOAP Web Service 编写集成测试。

2、测试 Spring Web Service

在 Spring Web Service 中,端点是服务器端 Service 实现的关键概念。专门的 @Endpoint 注解将类标记为 Web Service 端点。这些端点负责接收 XML 请求消息、调用所需的业务 Service 并将结果作为响应消息返回。

2.1、Spring Web Service 的测试支持

为了测试此类端点,我们可以通过传递所需的参数或模拟(Mock)来轻松创建单元测试。然而,这样做的主要缺点是无法实际测试通过网络发送的 XML 消息的内容。另一种方法是创建集成测试,以验证消息的 XML 内容。

Spring Web Service 2.0 引入了对此类端点集成测试的支持。提供这种支持的核心类是 MockWebServiceClient。它提供了一个 Fluent API,用于向 Spring Application Context 中配置的适当端点发送 XML 消息。此外,我们还可以设置响应预期、验证响应 XML,并对端点执行完整的集成测试。

不过,这需要调用整个 Application Context,从而减慢测试执行速度。这通常是不可取的,尤其是当我们希望为特定 Web Service 端点创建快速、隔离的测试时。

2.2、Spring Boot @WebServiceServerTest

Spring Boot 2.6 通过 @WebServiceServerTest 注解扩展了 Web Service 测试支持。

我们可以将其用于仅关注 Web Service 而非加载整个 Application Context 的测试。换句话说,我们可以创建一个只包含所需 @Endpoint Bean 的测试片段,并使用 @MockBean 来模拟任何依赖。

这与 Spring Boot 已经提供的便捷测试片段注解(如 @WebMvcTest@DataJpaTest 等)非常相似。

3、建立示例项目

3.1、依赖

除了 Web Service 必须依赖外,还需要额外的 spring-ws-test 依赖(test scope):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
    <groupId>wsdl4j</groupId>
    <artifactId>wsdl4j</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.ws</groupId>
    <artifactId>spring-ws-test</artifactId>
    <version>3.1.3</version>
    <scope>test</scope>
</dependency>

3.2、Web Service 示例

接下来,创建一个简单的服务,为指定的 Product ID 返回一些 Product 数据:

@Endpoint
public class ProductEndpoint {

    @Autowired
    private ProductRepository productRepository;

    @ResponsePayload
    public GetProductResponse getProduct(@RequestPayload GetProductRequest request) {
        GetProductResponse response = new GetProductResponse();
        response.setProduct(productRepository.findProduct(request.getId()));
        return response;
    }
}

这里,我们用 @EndpointProductEndpoint 组件进行了注解,从而将其注册用于处理相应的 XML 请求。

getProduct 方法接收 request 对象,并从 Repository 中获取 Product 数据,然后返回响应。Repository 的细节在这里并不重要。在本例中,为了简单,可以使用一个简单的内存实现。

4、端点测试

最后,创建一个测试片段,验证 Web Service 对 XML 消息的正确处理:

@WebServiceServerTest
class ProductEndpointIntegrationTest {

    @Autowired
    private MockWebServiceClient client;

    @MockBean
    private ProductRepository productRepository;

    @Test
    void givenXmlRequest_whenServiceInvoked_thenValidResponse() throws IOException {
        Product product = createProduct();
        when(productRepository.findProduct("1")).thenReturn(product);

        StringSource request = new StringSource(
          "<bd:getProductRequest xmlns:bd='http://baeldung.com/spring-boot-web-service'>" + 
            "<bd:id>1</bd:id>" + 
          "</bd:getProductRequest>"
        );
        
        StringSource expectedResponse = new StringSource(
          "<bd:getProductResponse xmlns:bd='http://baeldung.com/spring-boot-web-service'>" + 
            "<bd:product>" + 
              "<bd:id>1</bd:id>" + 
              "<bd:name>Product 1</bd:name>" + 
            "</bd:product>" + 
          "</bd:getProductResponse>"
        );

        client.sendRequest(withPayload(request))
          .andExpect(noFault())
          .andExpect(validPayload(new ClassPathResource("webservice/products.xsd")))
          .andExpect(payload(expectedResponse))
          .andExpect(xpath("/bd:getProductResponse/bd:product[1]/bd:name", NAMESPACE_MAPPING)
            .evaluatesTo("Product 1"));
    }
}

在这里,我们只为集成测试配置了带有 @Endpoint 注解的 Bean。换句话说,这个测试片段创建了一个精简的 Application Context。这帮助我们构建有针对性且快速的集成测试,而不会因为反复加载整个 Application Context 而影响性能。

重要的是,该注解还配置了一个 MockWebServiceClient 以及其他相关的自动配置。因此,我们可以将该客户端注入到我们的测试中,并用它来发送 getProductRequest XML 请求,然后进行各种 Fluent 式的期望操作。

预期(Expectation)验证响应 XML 是否与给定的 XSD schema 匹配,并且与预期的 XML 响应相匹配。还可以使用 XPath 表达式来评估和比较响应 XML 中的各种值。

4.1、Endpoint 的依赖

在这个示例中,我们使用 @MockBean 来模拟 ProductEndpoint 所需的 Repository。如果没有这个模拟,Application Context 就无法启动,因为自动配置被禁用了。换句话说,测试框架不会在测试执行前配置任何 @Component@Service@Repository Bean。

不过,如果我们确实需要实际的组件依赖,而不是模拟,那么可以使用 @Import 声明这些组件。Spring 会查找这些类,然后根据需要将它们注入到端点中。

4.2、加载整个 Context

如前所述,@WebServiceServerTest 不会加载整个 Application Context。如果我们确实需要加载整个 Application Context 进行测试,那么我们应该考虑把 @SpringBootTest@AutoConfigureMockWebServiceClient 结合使用。然后,可以以类似的方式使用该客户端来发送请求并验证响应,如前所述。

5、总结

本文介绍了 Spring Boot 中引入的 @WebServiceServerTest 注解,以及如何使用它为 Spring Boot 开发的 SOAP Web Service 构建快速、集中的集成测试。


参考:https://www.baeldung.com/spring-webserviceservertest