XML and SOAP

XML (Extensible Markup Language), defined by the W3C XML 1.0 Recommendation, stores data as text in named, nested elements such as <orderId>ORD-1042</orderId>. SOAP is a messaging protocol built on it: every request and response is an XML document called an envelope, and a WSDL file describes the service's operations and the XML each one accepts.

Example: to get an order's status, the client POSTs an envelope whose body holds GetOrderStatus and the order ID, and receives an envelope with the status or a SOAP fault saying what went wrong. Unlike REST, the operation is named inside the message, not by the method and the URI.

Why it matters

SOAP is common in integrations between organisations and in long-lived enterprise systems such as banking, payment, insurance, government and telecom interfaces, often behind a modern front end. A defect there can stop a whole business process, such as confirming a payment.

XML and SOAP also fail differently from JSON over REST. One unescaped & makes the whole message unreadable, an element in the wrong namespace is unknown although its name looks right, and SOAP 1.1 returns HTTP 500 even for bad input. Reading only status codes misses defects and invents false ones.

XML basics: well-formed, valid and namespaced

An element is a start tag, its content and an end tag, such as <total>249.90</total>; one with no content can be written <item/>. An attribute is a name="value" pair inside a start tag, such as currency="SAR" in the document below. A document has exactly one root element; every element is closed and properly nested, and names are case-sensitive, so <orderId> and <OrderId> are different elements. Attribute values are quoted, each attribute name appears once per element, and their order carries no meaning.

Following these rules makes a document well-formed; XML 1.0 makes any violation a fatal error, so the parser rejects the whole message instead of repairing it as browsers do with HTML. A valid document also matches a schema (XML 1.0 defines this for DTDs); web services use XML Schema (XSD) for required elements, order, types and allowed values.

  • Escaping: < and & must not appear raw in text; write &lt; and &amp; (XML also predefines &gt;, &quot; and &apos;). A CDATA section, <![CDATA[...]]>, carries text without escaping.
  • Encoding: the optional declaration <?xml version="1.0" encoding="UTF-8"?> names it; bytes that do not match garble Arabic or Hindi names or get the message rejected.
  • Namespaces: xmlns:ord="urn:example:orders" binds a prefix to a URI. An element is identified by URI plus local name, so the prefix does not matter, and the right name in the wrong namespace is unknown.
<?xml version="1.0" encoding="UTF-8"?>
<order id="ORD-1042" currency="SAR">
  <customer>Smith &amp; Sons</customer>
  <item sku="A-100" qty="2"/>
  <note><![CDATA[Leave at <b>reception</b>]]></note>
  <total>249.90</total>
</order>

Try it: XML Validator Try it: XML Formatter

The SOAP envelope and its HTTP binding

A SOAP message is an XML document with the root Envelope. The optional Header carries metadata such as a request ID or a security token; the mandatory Body carries the request or response element, or a fault. A header block marked mustUnderstand that the receiver cannot process must produce a MustUnderstand fault.

Requests usually go with POST to one endpoint URL shared by all operations. The two SOAP versions differ in the envelope namespace and the HTTP binding, so a request built for one version fails against a service that supports only the other.

A SOAP 1.1 request in two layers: HTTP headers carry the media type and SOAPAction, and the XML envelope's namespace sets the version, with an optional Header and a required Body naming the operation.

HTTP request line and headers

Part 1:POST /services/orders HTTP/1.1
Host: api.example.com
Part 2:Content-Type: text/xml; charset=utf-8
Part 3:SOAPAction: "urn:example:orders/GetOrderStatus"

XML body: the SOAP envelope

Part 4:<?xml version="1.0" encoding="UTF-8"?>
Part 5:<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               Part 6:xmlns:ord="urn:example:orders">
  Part 7:<soap:Header>
    <ord:RequestId>7f3c-0091</ord:RequestId>
  </soap:Header>
  Part 8:<soap:Body>
    Part 9:<ord:GetOrderStatus>
      Part 10:<ord:orderId>ORD-1042</ord:orderId>
    </ord:GetOrderStatus>
  </soap:Body>
</soap:Envelope>
  1. 1Usually POST to one endpointEvery operation shares this URL; the element in Body names the operation
  2. 2SOAP 1.1 media typeSOAP 1.2 uses application/soap+xml
  3. 3SOAPAction header, SOAP 1.1 onlyThe client must send it, with the value from the WSDL binding; SOAP 1.2 uses an action parameter instead
  4. 4XML declarationOptional; names the character encoding
  5. 5Envelope in the SOAP namespaceThe namespace sets the version; one the service does not support gets VersionMismatch
  6. 6Service namespaceThe schema's targetNamespace; the operation's elements must be in it
  7. 7Header, optionalMessage metadata such as a request ID; a block marked mustUnderstand must be processed or answered with a fault
  8. 8Body, requiredThe request or response element, or a Fault
  9. 9Operation elementIts name and namespace select the operation
  10. 10Parameter checked against the XSDRequired; must match ORD- plus 1 to 8 digits
  • SOAP 1.1 (a W3C Note, 2000): namespace http://schemas.xmlsoap.org/soap/envelope/, Content-Type: text/xml, and a mandatory SOAPAction header whose value the WSDL gives for each operation.
  • SOAP 1.2 (a W3C Recommendation since 2003): namespace http://www.w3.org/2003/05/soap-envelope, Content-Type: application/soap+xml, and the action, if any, as an action parameter of that header.
Text version
POST /services/orders HTTP/1.1
Host: api.example.com
Content-Type: text/xml; charset=utf-8
SOAPAction: "urn:example:orders/GetOrderStatus"

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:ord="urn:example:orders">
  <soap:Header>
    <ord:RequestId>7f3c-0091</ord:RequestId>
  </soap:Header>
  <soap:Body>
    <ord:GetOrderStatus>
      <ord:orderId>ORD-1042</ord:orderId>
    </ord:GetOrderStatus>
  </soap:Body>
</soap:Envelope>

Try it: SOAP Request Tester

WSDL: the contract of a SOAP service

A WSDL (Web Services Description Language) document is the machine-readable contract of a SOAP service, by convention often published at the endpoint with ?wsdl appended. WSDL 1.1 (a 2001 W3C Note) is still widely used; WSDL 2.0 (2007) renames portType to interface and port to endpoint. WSDL 1.1 has the five top-level elements listed below.

The XSD in types is the most precise part of your test basis. In the schema at the end of this section, orderId is required (minOccurs defaults to 1) and must match ORD- plus 1 to 8 digits, since XSD patterns match the whole value. includeItems is optional and accepts only true, false, 1 or 0; xs:sequence fixes the order; targetNamespace is the namespace the request elements must use.

  • types: XML Schema definitions of the request, response and fault elements.
  • message: the data each operation sends or receives.
  • portType: the operations with their input, output and declared faults.
  • binding: the SOAP version, document or rpc style, and each operation's soapAction.
  • service: the port's address, the URL you send requests to.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="urn:example:orders"
           elementFormDefault="qualified">
  <xs:element name="GetOrderStatus">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="orderId">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="ORD-[0-9]{1,8}"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:element>
        <xs:element name="includeItems" type="xs:boolean" minOccurs="0"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

SOAP faults: errors with a structure

A request that cannot be processed gets a Fault element inside Body. In SOAP 1.1 it holds faultcode, faultstring, an optional faultactor and a detail element, which must be present when the contents of Body could not be processed and carries application-specific data such as the business faults declared in the WSDL. SOAP 1.2 calls them Code (with a Value and an optional Subcode), Reason, Node, Role and Detail, of which only Code and Reason are required. Assert the fault code first:

  • Client (1.1) or Sender (1.2): the message is wrong — malformed, invalid or carrying a bad value — and fails again if resent unchanged.
  • Server (1.1) or Receiver (1.2): the message was fine but processing failed, for example because a database was down. It may succeed later.
  • VersionMismatch: an unsupported envelope namespace. MustUnderstand: a mandatory header block was not understood. SOAP 1.2 adds DataEncodingUnknown.
  • HTTP status: SOAP 1.1 requires 500 for every fault, Client faults included; SOAP 1.2 uses 400 for Sender faults and 500 for the rest. In SOAP 1.1 a 500 after bad input is therefore correct; the fault code decides.
HTTP/1.1 500 Internal Server Error
Content-Type: text/xml; charset=utf-8

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <soap:Fault>
      <faultcode>soap:Client</faultcode>
      <faultstring>orderId must match ORD- followed by 1 to 8 digits</faultstring>
      <detail>
        <ord:ValidationError xmlns:ord="urn:example:orders">
          <ord:field>orderId</ord:field>
          <ord:value>1042</ord:value>
        </ord:ValidationError>
      </detail>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

Worked example: testing GetOrderStatus

The order service runs SOAP 1.1 on a test environment the team owns. The tester derives checks from the WSDL and its schema, each with an expected result, and checks every hand-written envelope for well-formedness before sending it (the XML Validator checks well-formedness, not the XSD).

Responses are asserted with XPath, a query language for XML documents: //ord:status selects every status element in the namespace that the test tool binds to ord, whatever prefix the response itself uses. The tester never compares a response as text.

  • Valid request for ORD-1042: HTTP 200 and a GetOrderStatusResponse in the service namespace that validates against the XSD.
  • Unknown order ORD-99999999: the fault the WSDL declares, such as OrderNotFound, not a 200 with an empty body.
  • Not well-formed, such as </ord:orderID> closing <ord:orderId>: a Client fault, never an HTML page. No faultstring contains stack traces, class names, SQL or file paths.
  • Invalid: orderId missing, 1042 without the prefix, ORD- followed by no digits or by nine (just outside the 1 to 8 boundaries), includeItems set to yes, or the elements swapped: a Client fault naming the field. An accepted one means the service does not validate input; ask whether that is intended.
  • Envelope: an envelope in the SOAP 1.2 namespace, sent with text/xml, gets VersionMismatch; a SOAPAction that does not match the body never runs a different operation.
  • A <!DOCTYPE> before the envelope should be refused with a fault: both SOAP versions forbid a document type declaration in a message, and a parser that accepts one may be open to XML external entity (XXE) attacks. Run such checks only on systems you are authorised to test.

Try it: SOAP Request Tester Try it: XML Validator

Common mistakes

  • Judging a SOAP response by its HTTP status: in SOAP 1.1 a 500 can be the right answer to bad input, and a 200 can carry an error returned as data.
  • Comparing XML as text or asserting on prefixes such as ns2:; equivalent messages can differ in attribute order, indentation and prefixes.
  • Sending hand-written XML without checking that it is well-formed, then reporting the correct Client fault as a defect.
  • Testing only valid envelopes instead of breaking each required element, type, pattern and list of allowed values, and the element order.
  • Assuming the server validates requests against the XSD. That is a configuration choice: send an invalid message and see.

The QA perspective

On a SOAP project, start from the WSDL as you would from an OpenAPI document: list the operations and declared faults, and read the XSD for required elements, types and limits. Agree early which SOAP version is used, whether requests are schema-validated, and which business errors are faults rather than normal responses.

Keep request envelopes as versioned test data, one per check. In a bug report include the endpoint, the SOAP version, the SOAPAction, the full request and response envelopes and the HTTP status: in XML, one wrong character or namespace is often the whole defect.

Practice exercise

Review five responses from a SOAP 1.1 service

Your task

A colleague sent five requests to the CreateCustomer operation of a SOAP 1.1 service on the test environment and noted what was sent and what came back. Decide for each run whether the behaviour is correct or a defect, and why.

Material
Operation: CreateCustomer   SOAP 1.1   test environment
Schema:    name   xs:string, maxLength 50, required
           email  xs:string, required
WSDL:      declared fault DuplicateEmail

Run 1  sent: <cus:name>Smith &amp; Sons</cus:name>, valid email
       got:  200, <cus:name>Smith &amp; Sons</cus:name> in the response
Run 2  sent: <cus:Email>sara@example.com</cus:Email>  (no <cus:email>)
       got:  200, customer C-381 created with no email
Run 3  sent: valid request, email already registered
       got:  500, faultcode soap:Client, detail <cus:DuplicateEmail/>
Run 4  sent: valid request while the database is stopped
       got:  500, faultcode soap:Client, faultstring "Invalid request"
Run 5  sent: envelope without </soap:Body>
       got:  500, Content-Type text/html, page shows a Java stack trace
Steps
  1. For each run, write the expected HTTP status, fault code and body from the schema, the WSDL and the SOAP 1.1 rules.
  2. Mark each run as correct or defect, and give each defect a one-line bug title.
Show the model answer
Model answer

Run 1: correct. Expected 200 and the created customer: & is escaped as &amp; in XML and came back unchanged; the stored name is Smith & Sons.

Run 2: defect. XML names are case-sensitive, so <cus:Email> is unknown and the required email is missing; 500 with a Client fault naming email was due, so the service is not validating against its schema. Title: "CreateCustomer creates a customer without the required email".

Run 3: correct. SOAP 1.1 returns 500 for every fault, and Client with the declared DuplicateEmail detail tells the caller what to fix.

Run 4: defect. The request was valid, so the expected answer is 500 with a Server fault; a Client fault tells callers to change a correct message and stops their retries. Title: "CreateCustomer returns a Client fault when the database is down".

Run 5: defect. Malformed XML should get 500 and a Client fault in a SOAP envelope sent as text/xml; an HTML page breaks SOAP clients and the stack trace leaks internals. Title: "CreateCustomer answers malformed XML with an HTML stack trace".

Frequently asked questions

Is SOAP still worth learning if I already know REST?

Yes, if you will test integrations between organisations or long-lived enterprise systems, where SOAP remains common. Most of what you learned for REST carries over — contracts, negative tests, precise bug reports — and SOAP adds strict XML, a formal WSDL contract and structured faults.

What is the difference between well-formed and valid XML?

Well-formed means the document follows XML's syntax rules: one root, closed and properly nested elements, quoted attributes, escaped special characters. Valid means it is well-formed and also matches a schema, such as the XSD in a WSDL. A parser must reject a document that is not well-formed; a document can be well-formed and still invalid.

Can I test a SOAP service with a REST tool?

Yes. A SOAP request is an HTTP POST with an XML body, so any tool that sends a raw body with custom headers works. You set Content-Type yourself and, for SOAP 1.1, SOAPAction. Dedicated SOAP tools add conveniences such as building requests from the WSDL.

How do I tell whether a service uses SOAP 1.1 or 1.2?

Look at the WSDL binding: a SOAP 1.1 binding uses the namespace http://schemas.xmlsoap.org/wsdl/soap/, a SOAP 1.2 binding http://schemas.xmlsoap.org/wsdl/soap12/. A service may offer both, with one port each. In a working request, the envelope namespace and the Content-Type (text/xml or application/soap+xml) tell you the same.

References

Practise with Mutqan tools

Send SOAP 1.1 / 1.2 envelopes with SOAPAction headers and view the formatted XML response.

API Testing

Check XML well-formedness and report errors with line and column.

Formatting

Pretty-print XML with configurable indentation, and minify it back.

Formatting

Convert XML to JSON, preserving attributes, text nodes and repeated elements as arrays.

JSON & Data

Go deeper

Your progress on this topic

Part of the QA Roadmap