How to Mock a SOAP API Without Java
SOAP mocking usually means WireMock, XML configuration files, and a JVM. Here's how to do it with one command and zero dependencies.
Every time I’ve had to integrate with a SOAP API, the testing story has gone one of two ways: either someone spins up WireMock with a JVM, twenty XML config files, and a prayer — or the team just hardcodes a staging URL and hopes the vendor’s sandbox doesn’t go down during the CI run.
I’ve lived both of those. Neither is fun.
SoapUI is another option people reach for, but it’s a 500MB desktop app that wants to be your entire test platform. Hand-writing SOAP envelopes in curl? Sure, if you enjoy debugging mismatched namespace prefixes at 11pm. Most teams I’ve talked to just skip testing their SOAP integrations entirely. I get it. The tooling makes it feel not worth the effort.
That’s why I made SOAP a first-class protocol in mockd. Here’s what it looks like.
One command, one mock
Install mockd:
# macOS
brew install getmockd/tap/mockd
# Linux / Windows
curl -fsSL https://get.mockd.io | sh Create a SOAP mock:
mockd soap add
--path /soap/UserService
--action GetUser
--response '<User><Id>123</Id><Name>John Doe</Name></User>' That’s it. You’ve got a SOAP endpoint on localhost:4280. Hit it with a proper SOAP 1.1 request:
curl -X POST http://localhost:4280/soap/UserService
-H "Content-Type: text/xml; charset=utf-8"
-H "SOAPAction: "GetUser""
-d '<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetUser>
<id>123</id>
</GetUser>
</soap:Body>
</soap:Envelope>' <?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<User>
<Id>123</Id>
<Name>John Doe</Name>
</User>
</soap:Body>
</soap:Envelope> No Java. No JVM. No XML configuration files. mockd wraps your response in a proper SOAP envelope automatically.
One thing worth knowing: mockd matches incoming requests by SOAPAction header first, then falls back to the body element name. So if your client sends the header, great — fast match. If it doesn’t, mockd inspects the body and figures it out. It also auto-detects SOAP 1.1 vs 1.2 based on the namespace in the request envelope, so you don’t have to configure that.
Import from WSDL
If you already have a WSDL file (and if you’re dealing with SOAP, you probably do), you can skip writing mocks by hand entirely:
mockd soap import service.wsdl mockd parses the WSDL, extracts all operations, and generates sample responses from the XSD type definitions. If a GetUser operation returns a UserType with Id (integer) and Name (string), you’ll get a mock that returns plausible sample data automatically.
Currently WSDL 1.1 is supported. If you try to import a WSDL 2.0 file, mockd rejects it with a clear error explaining why and what to do — not a cryptic stack trace.
mockd also serves the WSDL itself, so your client can discover the service:
curl http://localhost:4280/soap/UserService?wsdl If you didn’t import a WSDL, mockd auto-generates one based on your configured operations. It won’t be as detailed as a hand-written WSDL, but it’s enough for client libraries that need a WSDL to generate stubs.
Dynamic responses with XPath
Static responses are fine for basic tests, but real SOAP services echo request data back. mockd supports XPath template variables for this:
mockd soap add
--path /soap/UserService
--action GetUser
--response '<User><Id>{{xpath://GetUser/id}}</Id><Name>John Doe</Name></User>' Now when a request comes in with <id>456</id> in the body, the response contains <Id>456</Id>. All the other template variables work in SOAP responses too — {{uuid}} for unique IDs, {{now}} for timestamps, {{faker.name}} for realistic names, {{faker.email}} for email addresses.
You can also route requests conditionally using XPath matching. The match.xpath field lets you return different responses based on request content — return a specific user for ID 1, a different one for ID 2, and a fault for anything else.
SOAP faults
You need to test error paths too. SOAP faults are configured in your mockd.yaml:
operations:
DeleteUser:
soapAction: "http://example.com/DeleteUser"
fault:
code: "soap:Client"
message: "Authorization required"
detail: "<ErrorCode>AUTH_001</ErrorCode>" When a client hits that operation, they get back a proper SOAP fault envelope:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>Authorization required</faultstring>
<detail>
<ErrorCode>AUTH_001</ErrorCode>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope> Exactly like a real service would return when something goes wrong. No more skipping error handling tests because setting up fault responses is too much work.
YAML config for complex services
When you’re mocking a full SOAP service with multiple operations, a config file is cleaner:
# mockd.yaml
version: "1.0"
mocks:
- name: User SOAP Service
type: soap
soap:
path: /soap/UserService
wsdl: ./UserService.wsdl
operations:
GetUser:
response: |
<User>
<Id>{{xpath://GetUser/id}}</Id>
<Name>{{faker.name}}</Name>
<Email>{{faker.email}}</Email>
<CreatedAt>{{now}}</CreatedAt>
</User>
ListUsers:
response: |
<Users>
<User><Id>1</Id><Name>Alice Martin</Name></User>
<User><Id>2</Id><Name>Bob Chen</Name></User>
</Users>
CreateUser:
response: |
<CreateUserResult>
<Id>{{uuid}}</Id>
<Status>CREATED</Status>
</CreateUserResult>
DeleteUser:
fault:
code: "soap:Client"
message: "Insufficient permissions" mockd serve --config mockd.yaml Four operations, one file, no JVM.
Stateful SOAP mocks
Here’s where it gets interesting. mockd can manage state across SOAP calls, just like it does for REST:
mockd soap add
--path /soap/UserService
--action CreateUser
--stateful-resource users
--stateful-action create Now CreateUser actually stores data, GetUser retrieves it, ListUsers returns everything, and DeleteUser removes it. You get automatic CRUD behavior over SOAP without writing any logic.
And because mockd’s stateful layer is protocol-agnostic, the same data is accessible from REST and SOAP simultaneously. Create a user through the SOAP endpoint, read it back through GET /api/users/1 on REST. Same in-memory store, different protocol frontends. This is genuinely useful when you’re migrating from SOAP to REST and need both interfaces to behave consistently during the transition.
What it doesn’t do
WSDL 1.1 only. If you try to import a WSDL 2.0 file, mockd rejects it with a clear error message — it doesn’t crash or silently produce garbage. But WSDL 2.0 support isn’t there yet.
XPath templates work well for most cases. That said, complex XPath expressions with namespace prefixes can get tricky. Simple paths like //UserId/text() are fine; deeply namespaced queries might take some fiddling.
The auto-generated WSDL (when you define operations via CLI or YAML without providing your own WSDL) is basic. It’s enough for client libraries that need a WSDL to generate stubs, but it won’t look like a hand-crafted WSDL with detailed type schemas. If your client needs a precise WSDL, bring your own.
There’s no WS-Security support. No UsernameToken, no X.509 signing, no SAML. If you’re testing a service that requires WS-Security headers, you’ll need to handle that on the client side or strip it from your test flow. This might change eventually, but I’m being honest about where things stand.
Try it
If you’re stuck integrating with a SOAP API and the testing story is “we don’t test that part” — give mockd a shot. One binary, no dependencies, and SOAP is treated as a real protocol instead of an afterthought bolted on with plugins.
- GitHub: github.com/getmockd/mockd (Apache 2.0)
- SOAP docs: docs.mockd.io/protocols/soap
- Install:
brew install getmockd/tap/mockdorcurl -fsSL https://get.mockd.io | sh - All 7 supported protocols: mockd.io/protocols — HTTP, gRPC, GraphQL, WebSocket, MQTT, SSE, SOAP
Try mockd
Multi-protocol API mock server. HTTP, gRPC, GraphQL, WebSocket, MQTT, SSE, SOAP.