How to test the Web Services of your Spring Boot Application with @WebServiceServerTest


The idea of applications that provide their services over the web is anything but new. Chances are quite high that you already implemented such a service through a REST API. But before REST, there was a different approach to providing such services - SOAP. The release of Spring Boot 2.6.0 introduced a new test slice to test the components involved in providing these services. Let’s have a look at it!

What is a Web Service?

In general, a web service is an interface that allows machine-to-machine communication. It is used for data exchange or the execution of functions in another system. In this blog post, we will talk about web services that can be accessed via the SOAP protocol. SOAP itself is a W3C standard that defines the way how the systems communicate with each other. It uses messages in XML format that are sent – mostly, but not exclusively – over HTTP.
Through Spring Web Services, Spring offers a convenient way to develop these kinds of services.

Spring Web Services (Spring-WS) is a product of the Spring community focused on creating document-driven Web services. Spring Web Services aims to facilitate contract-first SOAP service development, allowing for the creation of flexible web services …

As those services are used to integrate different applications, they can make or break our complete software system. To reduce the risk of breaking something we should implement tests to prevent errors or unwanted changes in the interface. Luckily for us, Spring Boot offers a test slice for this purpose – the @WebServiceServerTest.

What is @WebServiceServerTest

The @WebServiceServerTest is another one of Spring Boot’s test slices. Like the others, it is used for integration tests that focus on a specific part of your application. In this case the Spring Web Service Endpoints. By annotating a test with @WebServiceServerTest, the full auto-configuration of the application context is disabled. However, the beans relevant for web services (Endpoints, EndpointInterceptor) are configured. On top of that Spring Boot will also configure a MockWebServiceClient which can be used in our tests to access the services.

The Example

Before we start the implementation of the tests I will first introduce the web service endpoints that will be tested. This blog post will not contain any details about how the endpoints are implemented, just how they can be tested. If you are interested in implementing a service yourself, have a look at the Resources below.

BookEndpoint.java
@Endpoint
public class BookEndpoint {

    private static final String NAMESPACE_URI = "http://jschmitz.dev/spring-boot-webserviceservertest/webservice/model";

    private final BookRepository bookRepository;

    public BookEndpoint(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "getBooksRequest")
    @ResponsePayload
    public GetBooksResponse getBooks(){

        var books = bookRepository.findAll();

        var response = new GetBooksResponse();
        response.getBooks().addAll(books);

        return response;
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "getBookRequest")
    @ResponsePayload
    public GetBookResponse getBookByIsbn(@RequestPayload GetBookRequest request){

        var book = bookRepository.findByIsbn(request.getIsbn())
                .orElseThrow(() -> new BookNotFoundException("Can not find Book with ISBN '%s'".formatted(request.getIsbn())));

        var response = new GetBookResponse();
        response.setBook(book);

        return response;
    }
}

The BookEndpoint provides methods to retrieve books from the system. You can either retrieve a list of all Books or you can search for a specific book by its ISBN. If the provided ISBN does not match any book, an Exception is thrown. To keep this example as simple as possible the BookRepository just holds an In-Memory List of books. In a real project, it would probably query a database.

Test Setup

Let’s start by adding a new dependency to our project:

pom.xml
<dependency>
    <groupId>org.springframework.ws</groupId>
    <artifactId>spring-ws-test</artifactId>
    <scope>test</scope>
</dependency>

spring-ws-test contains classes for testing servers that provide web services, clients that consume web services, and helper classes to create and validate SOAP messages.

With the dependency added we are ready to write our first test.

BookEndpointTest.java
@WebServiceServerTest
class BookEndpointTest {

    @Autowired
    private MockWebServiceClient client;

    @Test
    void returnsListOfBooks() {

        final var request = "<js:getBooksRequest"+
                            "   xmlns:js=\"http://jschmitz.dev/spring-boot-webserviceservertest/webservice/model\""+
                            "/>";

        this.client
                .sendRequest(withPayload(new StringSource(request)))
                .andExpect(noFault());
    }
}

  • By annotating the test class with @WebServiceServerTest Spring Boot will configure all beans related to web services
  • The MockWebServiceClient is a helper class from the spring-ws-test dependency. We will use it to send requests to our web services and make assertions about the response.
  • The RequestCreators::withPayload method is used to create the content of the message we will send.
  • ResponseMatchers::noFault is used to make an assertion about the response we receive from the web service. In this case, we expect that the web service will not return an error.

Let’s keep going by extending this test. Currently, we only check that the web service does not return an error. We should also check, that the service returns the data we expect.

BookEndpointTest.java
@WebServiceServerTest
class BookEndpointTest {

    private static final Map<String, String> NAMESPACE_MAPPING = Map.ofEntries(
            Map.entry("js", "http://jschmitz.dev/spring-boot-webserviceservertest/webservice/model")
    );

    @Autowired
    private MockWebServiceClient client;

    @MockBean
    private BookRepository bookRepository;

    private static Book createBook(String isbn, String title, String author) {
        //helper to create a book
    }

    @Test
    void returnsListOfBooks() {

        var testBook = createBook("978-3-86490-525-4", "Spring Boot 2", "Michael Simons");
        var testBook2 = createBook("978-3-86490-673-2", "Sustainable Software Architecture", "Carola Lilienthal");

        when(bookRepository.findAll()).thenReturn(List.of(testBook, testBook2));

        final var request = "<js:getBooksRequest"+
                            "   xmlns:js=\"http://jschmitz.dev/spring-boot-webserviceservertest/webservice/model\""+
                            "/>";

        this.client
                .sendRequest(withPayload(new StringSource(request)))
                .andExpect(noFault())
                .andExpect(xpath("/js:getBooksResponse/js:books[1]/js:isbn", NAMESPACE_MAPPING).evaluatesTo(testBook.getIsbn()))
                .andExpect(xpath("/js:getBooksResponse/js:books[1]/js:title", NAMESPACE_MAPPING).evaluatesTo(testBook.getTitle()))
                .andExpect(xpath("/js:getBooksResponse/js:books[1]/js:author", NAMESPACE_MAPPING).evaluatesTo(testBook.getAuthor()))
                .andExpect(xpath("/js:getBooksResponse/js:books[2]", NAMESPACE_MAPPING).exists());
    }
  • The mapping of an XML namespace to its URI. This will be used for the evaluation of an XPath later in the test.
  • The mock of a BookRepository that will be injected into our web service.
  • We configure the behavior of our BookRepository mock. It should always return the list of books we specified.
  • We use ResponseMatchers::xpath to make expectations about the content of the message.
    CAUTION: XPaths are one-based!

Conclusion

The WebServiceServerTest is a neat addition to the existing test slices. In combination with the helper classes provided by springs-ws-test, testing your web services becomes fairly easy. This is especially true if you already have experience with MockMvcTest because they have a similar approach.

Resources