Spring SOAP Testing
Testing is a critical part of developing robust Spring SOAP web services. In this guide, you'll learn how to effectively test your SOAP services, ensuring they function correctly before deployment. We'll cover both unit and integration testing approaches, using Spring's testing framework alongside other helpful tools.
Introduction to SOAP Testing
SOAP (Simple Object Access Protocol) web services require thorough testing to ensure they correctly handle requests, process data, and return appropriate responses according to their WSDL (Web Services Description Language) contracts. Spring provides excellent testing support for SOAP services through various tools and utilities.
Testing SOAP services involves:
- Unit testing - Testing individual components in isolation
- Integration testing - Testing the entire flow from request to response
- Contract validation - Ensuring your service adheres to its WSDL contract
Prerequisites
Before diving into testing, make sure you have the following dependencies in your project:
<dependencies>
<!-- Spring Web Services -->
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-core</artifactId>
</dependency>
<!-- Testing dependencies -->
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>xmlunit</groupId>
<artifactId>xmlunit</artifactId>
<version>1.6</version>
<scope>test</scope>
</dependency>
</dependencies>
Setting Up a Sample SOAP Service
Before testing, let's set up a simple SOAP service to work with. We'll create a country information service:
- First, define an XSD schema (
countries.xsd
):
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://spring.io/guides/gs-producing-web-service"
targetNamespace="http://spring.io/guides/gs-producing-web-service">
<xs:element name="getCountryRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="getCountryResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="country" type="tns:country"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="country">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="population" type="xs:int"/>
<xs:element name="capital" type="xs:string"/>
<xs:element name="currency" type="tns:currency"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="currency">
<xs:restriction base="xs:string">
<xs:enumeration value="EUR"/>
<xs:enumeration value="USD"/>
<xs:enumeration value="GBP"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
-
Generate Java classes from XSD using Maven or Gradle plugin
-
Create a repository class:
@Component
public class CountryRepository {
private static final Map<String, Country> countries = new HashMap<>();
@PostConstruct
public void initData() {
Country spain = new Country();
spain.setName("Spain");
spain.setCapital("Madrid");
spain.setCurrency(Currency.EUR);
spain.setPopulation(46704314);
countries.put(spain.getName(), spain);
// Add more countries...
}
public Country findCountry(String name) {
return countries.get(name);
}
}
- Create an endpoint:
@Endpoint
public class CountryEndpoint {
private static final String NAMESPACE_URI = "http://spring.io/guides/gs-producing-web-service";
private CountryRepository countryRepository;
@Autowired
public CountryEndpoint(CountryRepository countryRepository) {
this.countryRepository = countryRepository;
}
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")
@ResponsePayload
public GetCountryResponse getCountry(@RequestPayload GetCountryRequest request) {
GetCountryResponse response = new GetCountryResponse();
response.setCountry(countryRepository.findCountry(request.getName()));
return response;
}
}
- Configure the web service:
@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
@Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(
ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(servlet, "/ws/*");
}
@Bean(name = "countries")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("CountriesPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://spring.io/guides/gs-producing-web-service");
wsdl11Definition.setSchema(countriesSchema);
return wsdl11Definition;
}
@Bean
public XsdSchema countriesSchema() {
return new SimpleXsdSchema(new ClassPathResource("countries.xsd"));
}
}
Unit Testing SOAP Endpoints
Unit tests focus on testing the endpoint in isolation, mocking any dependencies:
@RunWith(SpringRunner.class)
public class CountryEndpointTest {
private CountryRepository countryRepository;
private CountryEndpoint endpoint;
@Before
public void setUp() {
countryRepository = mock(CountryRepository.class);
endpoint = new CountryEndpoint(countryRepository);
}
@Test
public void testGetCountry() {
// Create test data
Country spain = new Country();
spain.setName("Spain");
spain.setCapital("Madrid");
spain.setCurrency(Currency.EUR);
spain.setPopulation(46704314);
// Configure mock
when(countryRepository.findCountry("Spain")).thenReturn(spain);
// Create request
GetCountryRequest request = new GetCountryRequest();
request.setName("Spain");
// Call endpoint and verify response
GetCountryResponse response = endpoint.getCountry(request);
assertNotNull(response);
assertNotNull(response.getCountry());
assertEquals("Spain", response.getCountry().getName());
assertEquals("Madrid", response.getCountry().getCapital());
assertEquals(Currency.EUR, response.getCountry().getCurrency());
assertEquals(46704314, response.getCountry().getPopulation());
// Verify the mock was called
verify(countryRepository).findCountry("Spain");
}
}
Integration Testing SOAP Services
Integration tests verify the entire flow, including XML serialization/deserialization and proper SOAP message handling:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CountryEndpointIntegrationTest {
@Autowired
private ApplicationContext applicationContext;
private MockWebServiceClient mockClient;
@Before
public void setUp() {
mockClient = MockWebServiceClient.createClient(applicationContext);
}
@Test
public void testGetCountryRequest() throws Exception {
// Create test request
Source requestPayload = new StringSource(
"<gs:getCountryRequest xmlns:gs=\"http://spring.io/guides/gs-producing-web-service\">" +
"<gs:name>Spain</gs:name>" +
"</gs:getCountryRequest>"
);
// Create expected response
Source responsePayload = new StringSource(
"<gs:getCountryResponse xmlns:gs=\"http://spring.io/guides/gs-producing-web-service\">" +
"<gs:country>" +
"<gs:name>Spain</gs:name>" +
"<gs:population>46704314</gs:population>" +
"<gs:capital>Madrid</gs:capital>" +
"<gs:currency>EUR</gs:currency>" +
"</gs:country>" +
"</gs:getCountryResponse>"
);
// Execute request and verify response
mockClient
.sendRequest(withPayload(requestPayload))
.andExpect(noFault())
.andExpect(payload(responsePayload))
.andExpect(validPayload(new ClassPathResource("countries.xsd")));
}
}
Testing SOAP Faults
SOAP services should return appropriate SOAP faults when errors occur. Here's how to test them:
@Test
public void testCountryNotFound() throws Exception {
// Create test request for a non-existent country
Source requestPayload = new StringSource(
"<gs:getCountryRequest xmlns:gs=\"http://spring.io/guides/gs-producing-web-service\">" +
"<gs:name>Unknown</gs:name>" +
"</gs:getCountryRequest>"
);
// Execute request and expect a SOAP fault
mockClient
.sendRequest(withPayload(requestPayload))
.andExpect(serverOrReceiverFault("Country not found"));
}
For this to work, you need to update your endpoint to handle not found countries:
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")
@ResponsePayload
public GetCountryResponse getCountry(@RequestPayload GetCountryRequest request) {
Country country = countryRepository.findCountry(request.getName());
if (country == null) {
throw new CountryNotFoundException("Country not found");
}
GetCountryResponse response = new GetCountryResponse();
response.setCountry(country);
return response;
}
// Custom exception
public class CountryNotFoundException extends RuntimeException {
public CountryNotFoundException(String message) {
super(message);
}
}
// Exception handling
@Component
public class SoapFaultExceptionResolver extends SoapFaultMappingExceptionResolver {
public SoapFaultExceptionResolver() {
setDefaultFault(SoapFault.SERVER);
Map<String, String> faultMappings = new HashMap<>();
faultMappings.put(CountryNotFoundException.class.getName(), SoapFault.SERVER.name());
setExceptionMappings(faultMappings);
}
@Override
protected SoapFaultDefinition getFaultDefinition(Object endpoint, Exception ex) {
SoapFaultDefinition faultDefinition = super.getFaultDefinition(endpoint, ex);
if (ex instanceof CountryNotFoundException) {
faultDefinition.setFaultStringOrReason(ex.getMessage());
}
return faultDefinition;
}
}
Testing SOAP Clients
If your application consumes external SOAP services, you'll need to test your client code as well:
@RunWith(SpringRunner.class)
public class CountryClientTest {
private CountryClient client;
private MockWebServiceServer mockServer;
private Resource responsePayload;
@Before
public void setUp() {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
mockServer = MockWebServiceServer.createServer(webServiceTemplate);
client = new CountryClient(webServiceTemplate);
responsePayload = new ClassPathResource("test-responses/spain-response.xml");
}
@Test
public void testGetCountrySuccess() {
mockServer
.expect(payload(new ClassPathResource("test-requests/spain-request.xml")))
.andRespond(withPayload(responsePayload));
Country country = client.getCountry("Spain");
assertNotNull(country);
assertEquals("Spain", country.getName());
assertEquals("Madrid", country.getCapital());
mockServer.verify();
}
}
Create the test XML files in your resources directory:
test-requests/spain-request.xml:
<gs:getCountryRequest xmlns:gs="http://spring.io/guides/gs-producing-web-service">
<gs:name>Spain</gs:name>
</gs:getCountryRequest>
test-responses/spain-response.xml:
<gs:getCountryResponse xmlns:gs="http://spring.io/guides/gs-producing-web-service">
<gs:country>
<gs:name>Spain</gs:name>
<gs:population>46704314</gs:population>
<gs:capital>Madrid</gs:capital>
<gs:currency>EUR</gs:currency>
</gs:country>
</gs:getCountryResponse>
Best Practices for SOAP Testing
- Separate concerns: Test XML marshalling/unmarshalling separately from business logic
- Use XMLUnit: For more complex XML comparisons and validation
- Validate against schemas: Ensure your requests and responses are valid according to your XSD
- Test error conditions: Verify that your service handles errors appropriately
- Use meaningful test data: Create test data that represents real-world scenarios
- Mock external dependencies: Avoid calling real external services in tests
Real-World Testing Example
Let's create a more complex example that includes validation and error handling:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CompleteCountryEndpointTest {
@Autowired
private ApplicationContext applicationContext;
private MockWebServiceClient mockClient;
private XmlValidator validator;
@Before
public void setUp() {
mockClient = MockWebServiceClient.createClient(applicationContext);
validator = new XmlValidator();
}
@Test
public void testValidRequest() throws Exception {
// Valid request
Source requestPayload = new StringSource(
"<gs:getCountryRequest xmlns:gs=\"http://spring.io/guides/gs-producing-web-service\">" +
"<gs:name>Spain</gs:name>" +
"</gs:getCountryRequest>"
);
mockClient
.sendRequest(withPayload(requestPayload))
.andExpect(noFault())
.andExpect(xpath("/gs:getCountryResponse/gs:country/gs:name",
Map.of("gs", "http://spring.io/guides/gs-producing-web-service"))
.evaluatesTo("Spain"))
.andExpect(xpath("/gs:getCountryResponse/gs:country/gs:population",
Map.of("gs", "http://spring.io/guides/gs-producing-web-service"))
.evaluatesTo("46704314"))
.andExpect(xpath("/gs:getCountryResponse/gs:country/gs:capital",
Map.of("gs", "http://spring.io/guides/gs-producing-web-service"))
.evaluatesTo("Madrid"))
.andExpect(validPayload(new ClassPathResource("countries.xsd")));
}
@Test
public void testPerformanceWithMultipleRequests() throws Exception {
String[] countries = {"Spain", "Poland", "UK"};
for (String country : countries) {
Source requestPayload = new StringSource(
"<gs:getCountryRequest xmlns:gs=\"http://spring.io/guides/gs-producing-web-service\">" +
"<gs:name>" + country + "</gs:name>" +
"</gs:getCountryRequest>"
);
mockClient
.sendRequest(withPayload(requestPayload))
.andExpect(noFault())
.andExpect(xpath("/gs:getCountryResponse/gs:country/gs:name",
Map.of("gs", "http://spring.io/guides/gs-producing-web-service"))
.evaluatesTo(country));
}
}
}
// Helper class for XML validation
class XmlValidator {
public boolean validateAgainstXSD(String xml, Resource xsdSchema) {
try {
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = factory.newSchema(xsdSchema.getFile());
Validator validator = schema.newValidator();
validator.validate(new StreamSource(new StringReader(xml)));
return true;
} catch (Exception e) {
return false;
}
}
}
Summary
Testing SOAP web services in Spring is comprehensive and well-supported through the Spring-WS Test module. By writing both unit and integration tests, you can ensure your SOAP services function correctly and handle various scenarios appropriately.
In this guide, we covered:
- Setting up a testing environment for Spring SOAP services
- Writing unit tests for endpoints
- Creating integration tests with MockWebServiceClient
- Testing error conditions and SOAP faults
- Testing SOAP clients
- Best practices for SOAP service testing
With these testing approaches, you can build robust SOAP web services that handle real-world requirements reliably.
Additional Resources
- Spring Web Services Documentation
- Spring WS Test Documentation
- XMLUnit Documentation
- SOAP 1.1 Specification
Exercises
- Create a test that validates a SOAP request against an XSD schema before sending it
- Implement a test for a SOAP client that handles timeouts gracefully
- Write a test for a service that processes batch requests (multiple countries in one request)
- Create a test that verifies SOAP headers are correctly processed
- Implement performance tests for your SOAP service to ensure it meets response time requirements
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)