Testing SOAP web services with Spring-WS and MockWebServiceServer
SOAP remains important in Australian enterprise software, even as REST and event-driven APIs receive most of the attention. Banks, insurers, government departments, utilities and large healthcare providers often depend on XML contracts that have been running for years. A reliable test suite needs to protect those contracts while still allowing services to evolve.
Spring Web Services (Spring-WS) provides a focused way to build SOAP clients and endpoints in Java. Its MockWebServiceServer component lets developers test client behaviour without deploying a real remote service, starting an application server or relying on an external test environment.
This approach is especially useful for backend teams in Sydney, Melbourne and Brisbane, where a service may call systems owned by separate vendors or business units. Tests can verify SOAP actions, namespaces, headers, payloads and fault handling before a change reaches a shared integration environment.
The examples below use JUnit-style tests and XML matchers, although the same ideas work with Spock and Groovy. Teams already practising automated UI testing may find that the same habits around readable specifications and isolated fixtures apply well to SOAP integration tests; automated UI testing provides useful context for that broader testing style.
Why SOAP contracts still matter
A SOAP request is more than an HTTP POST containing XML. The envelope, body, namespaces, headers and operation-specific schema all form part of the contract. A request can be well-formed XML and still be rejected because it uses the wrong namespace URI, omits a required element or sends an unexpected SOAP action.
Spring-WS represents these details through message factories, marshallers, endpoint mappings and interceptors. Client code commonly uses WebServiceTemplate, which sends a marshalled request to a configured destination and converts the response back into a domain object. Testing that interaction gives the team confidence in the integration boundary rather than just the Java method that prepares the request.
Contract tests are valuable when the provider is expensive, slow or difficult to access. A real banking or government endpoint may require VPN access, client certificates, IP allow-listing and carefully managed test credentials. In Australian organisations, those restrictions can make local development awkward, especially when staff work remotely or across different states and time zones.
A mock server does not replace a small number of tests against the real provider. Instead, it handles fast and repeatable checks during development and continuous integration, while a controlled environment verifies deployment configuration and provider compatibility.
Setting up MockWebServiceServer
The usual test arrangement creates a WebServiceTemplate, attaches a MockWebServiceServer, configures an expectation and invokes the production client. The server intercepts messages sent through that template and returns a response supplied by the test.
A typical Maven dependency uses the Spring-WS test module alongside the normal Spring-WS client dependency. The exact version should match the Spring Framework and Spring-WS versions used by the application. With Spring Boot, dependency management usually controls those versions, reducing the chance of mixing incompatible libraries.
A compact JUnit example looks like this:
class CustomerClientTest {
private WebServiceTemplate template;
private MockWebServiceServer server;
private CustomerClient client;
@BeforeEach
void setUp() {
template = new WebServiceTemplate();
server = MockWebServiceServer.createServer(template);
client = new CustomerClient(template, "http://example.test/customer");
}
@Test
void sendsCustomerLookupAndReadsResponse() {
server.expect(
RequestMatchers.payload(
new ClassPathResource("customer-request.xml")))
.andRespond(
ResponseCreators.withPayload(
new ClassPathResource("customer-response.xml")));
Customer customer = client.findById("C-1042");
assertThat(customer.name()).isEqualTo("Ava Taylor");
server.verify();
}
}
The important detail is server.verify(). Without verification, a test may pass while leaving an expectation unsatisfied. Keeping setup explicit also makes the test independent from a full application context, which improves speed in a Gradle or Maven build.
Matching XML without fragile tests
Spring-WS offers request matchers for payloads, SOAP envelopes, headers, XPath expressions and SOAP actions. Exact payload matching is useful when the complete XML structure is contractual, but it can become brittle when harmless formatting or optional elements change.
XPath assertions are often a better choice for business-critical fields. A test can check that /ns:FindCustomer/ns:id contains the expected identifier while ignoring irrelevant whitespace and generated values. Namespace declarations must be supplied correctly, because XPath prefixes are resolved by URI rather than by the prefix spelling used in the document.
Map<String, String> namespaces =
Map.of("c", "https://example.com/customer/v1");
server.expect(
RequestMatchers.xpath(
"/c:FindCustomer/c:id",
namespaces,
"C-1042"))
.andRespond(ResponseCreators.withPayload(response));
When a request includes a timestamp, correlation ID or generated UUID, do not copy a production value into a fixed fixture unless it is part of the contract. Match the stable XML elements and assert dynamic values separately in Java. This keeps the test focused on behaviour rather than serialisation noise.
The same principle applies to SOAP headers. A WS-Addressing Action, authentication header or correlation value may be essential, while transport-specific details may be incidental. Separate expectations can make failures easier to diagnose than one enormous XML comparison.
Covering faults, headers and edge cases
A successful response is only one path through a SOAP client. Provider faults, malformed responses, timeouts and authentication failures often cause the most damaging production incidents. MockWebServiceServer can return SOAP faults so the client’s exception mapping and logging can be tested without contacting a remote system.
A fault test should verify both the exception type and the information exposed to calling code. For example, a business fault such as “customer not found” may become a domain exception, while an authentication fault should trigger an operational alert or a controlled failure. Avoid asserting the entire exception message when it contains provider-specific wording that may change.
SOAP headers deserve their own tests when they carry security or routing information. Spring-WS interceptors can add WS-Security signatures, usernames, timestamps and custom headers. If the signing library is difficult to exercise in a unit test, use a focused integration test with the relevant message factory, then use MockWebServiceServer to verify the remaining client behaviour.
Asynchronous workflows need a different boundary. A mock server can confirm that a client submits the expected message and handles an acknowledgement, but it cannot prove that a queue, scheduler or provider callback behaves correctly. Pair the SOAP client tests with contract tests for the callback endpoint and persistence or messaging tests for the workflow around it.
Running reliable tests in continuous delivery
SOAP tests should run in the normal build, not as a manually selected suite that is forgotten before release. Fast mock-server tests are suitable for every pull request and every commit. A smaller group of environment tests can run after deployment, using credentials and endpoints supplied through a secure pipeline configuration.
This separation matters for Australian teams working across AEST, ACST and AWST. A test that depends on a shared provider may fail because another team is deploying, a certificate has expired or a scheduled maintenance window is underway in Perth while Melbourne developers are starting their day. Local mock tests provide immediate feedback without making the build dependent on those operational conditions.
Fixtures should be stored beside the tests and named by behaviour, such as customer-not-found-response.xml or invalid-address-request.xml. Reviewers can then see contract changes in the same pull request as the Java code. Avoid putting real customer information in fixtures; Australian privacy obligations and internal policies make synthetic data the safer default.
For CI diagnostics, log the request and response only when a test fails, and redact credentials, tokens and personal information. A useful failure should identify the matcher that failed, the relevant XPath or header, and the received XML. Snapshot-style output can help during development, but permanent logs should remain safe for shared build systems.
Choosing a balanced testing approach
No single test style covers every SOAP risk. MockWebServiceServer is excellent for client-side message construction and response handling, while schema validation, provider environments and end-to-end checks answer different questions. A balanced suite avoids both false confidence and slow, unreliable pipelines.
The following comparison helps place each test type in the wider strategy:
| Test approach | Main purpose | Speed | External dependency | Best use |
|---|---|---|---|---|
| MockWebServiceServer | Verify client requests and responses | Very fast | None | Pull requests and unit-level integration |
| XML schema validation | Check structural contract compliance | Fast | None | Build-time contract checks |
| Stubbed HTTP service | Exercise transport configuration | Fast | Local stub process | Headers, TLS and endpoint behaviour |
| Provider integration test | Confirm compatibility with a real service | Moderate to slow | Shared or vendor environment | Scheduled pipeline validation |
| End-to-end business test | Verify the complete workflow | Slowest | Multiple systems | Release confidence and critical journeys |
A practical test suite should keep most scenarios in the first two rows. Use provider tests for representative success, fault and authentication cases rather than duplicating every permutation remotely. This reduces queueing, network failures and pressure on shared environments.
Useful recommendations for maintaining the suite include:
- Keep production client configuration injectable so tests can provide a dedicated
WebServiceTemplate. - Match stable XML fields with XPath and reserve full-payload comparisons for strict contract cases.
- Add explicit tests for SOAP faults, missing fields, namespaces, headers and retry decisions.
- Use synthetic Australian-style addresses and identifiers rather than real customer records.
- Run a small real-provider smoke suite on a schedule, with certificates and secrets managed outside source control.
A mature Spring-WS test suite should make a broken contract obvious before it becomes an incident. Start with a mocked request and response for each operation, add targeted assertions for namespaces and headers, then cover faults and deployment-level integration separately. The practical takeaway is simple: use MockWebServiceServer for fast, deterministic feedback, and reserve real SOAP environments for the compatibility risks that a mock cannot represent.