Testing and debugging SOAP services

Generate a request from the WSDL, look at the XML that actually went on the wire, and assert on faults in automated tests.

Getting a request to run

# Record the exchange for a real service, then read the XML, not the framework error
curl -s -o response.xml -D headers.txt -X POST "https://example.com/OrderService" \
  -H 'Content-Type: application/soap+xml; charset=utf-8; action="urn:example:orders/PlaceOrder"' \
  --data-binary @request.xml

head -1 headers.txt
xmllint --format response.xml | head -40

# Validate the response against the schema from the WSDL before reading it
xmllint --noout --schema order.xsd response.xml && echo "schema valid"

# Replay the exact bytes through a recording proxy to compare a working and failing call
mitmdump -w flow.dump
mitmproxy -r flow.dump
  • SoapUI and ReadyAPI generate a skeleton request from the WSDL, including a mock service you can run to test the client before the server exists.
  • Postman can import a WSDL and produce an example request, which is enough for a quick check.
  • Always inspect the raw XML. Framework error messages describe a symptom several layers away from the actual malformed element.
  • Test a deliberate fault path: send a request that must fail and assert on the fault code and detail, not just on an exception being thrown.

Automated tests that fail for the right reason

import requests
from lxml import etree

NS = {"s": "http://www.w3.org/2003/05/soap-envelope"}

def call(body_xml: str) -> etree._Element:
    r = requests.post(
        "https://example.com/OrderService",
        data=body_xml.encode("utf-8"),
        headers={"Content-Type": ('application/soap+xml; charset=utf-8; '
                                  'action="urn:example:orders/PlaceOrder"')},
        timeout=10)
    return etree.fromstring(r.content)

def test_unknown_sku_returns_a_fault():
    resp = call("<PlaceOrder xmlns='urn:example:orders'><sku>NOPE</sku></PlaceOrder>")
    fault = resp.find(".//s:Fault", NS)
    assert fault is not None, "expected a fault, got a normal response"
    assert (resp.findtext(".//s:Code/s:Value", namespaces=NS)
            == "env:Sender")
    assert "SKU" in resp.findtext(".//s:Reason/s:Text", namespaces=NS, default="")
SymptomFirst thing to check
401 or 403 with valid credentialsWhether WS-Security or the gateway, not application authentication, is rejecting
Cannot resolve the operationThe action URI and namespace, exactly as declared in the WSDL
Parse error on the clientWhether the response is a fault being parsed as a success message
Hangs then times outA retry loop or an unbounded connection pool, not the service itself
Works with curl, fails in the frameworkThe framework is serialising an extra element or a different namespace prefix
💡
Keep a captured request and response pair for every operation in the repository. When a partner upgrades their stack, the diff between the saved fixture and the new message pinpoints the change in seconds, and it doubles as documentation of the exact wire format.

FAQ

What replaced SoapUI for new projects?
Postman or a thin script wrapper over HTTP for one-off calls, and a real test suite for regression coverage. SoapUI remains useful mainly for WSDL-driven mock services.
How do I test a service I cannot call repeatedly?
Record responses through a proxy, then run tests against the recordings. Re-record on a schedule so schema drift is still detected.

SOAP faults and error handling Modernising SOAP: wrapping and migrating to REST or gRPC

Last refreshed 2026-09-18.