TestingAPIs is still being built. If something breaks, email support@testingapis.com.

Reading a WSDL

The five parts of the contract a SOAP service publishes, and what tools do with it.

A WSDL, short for Web Services Description Language and usually pronounced "wiz-dul", is an XML document in which a SOAP service describes itself: which operations it has, what each one takes and returns, how to call them and where. It's the contract between the service and its clients. Tools read it to generate client code and sample requests, and testers read it to know what to test. This lesson goes through a real one, part by part.

Getting the WSDL

By convention, a SOAP service serves its WSDL at its own URL with ?wsdl added:

Terminal
$ curl -s "https://api.testingapis.com/api/soap/calculator?wsdl" | head -2
<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://tempuri.org/" targetNamespace="http://tempuri.org/">
This example uses endpoints for signed-in members.Sign inCreate a free account

The root element declares the namespaces the document uses. Five prefixes, each a different vocabulary, which is why lesson 2 came first: wsdl for WSDL itself, soap and soap12 for the SOAP-specific parts, s for XML Schema, and tns, "this namespace", for the service's own names. The targetNamespace, http://tempuri.org/, is the namespace every operation element must be in.

Five parts

A WSDL 1.1 document, which is the version nearly every SOAP service uses, has five kinds of section:

Diagram
definitions                 the whole document, with the namespaces it uses
├── types                   what the messages look like, as XML Schema
├── message (one per input and output)
├── portType                the operations: names, inputs, outputs        ┐ what the service does
├── binding (one per protocol)                                            ┘
│                           how to call it: SOAP version, style, SOAPAction ┐ how and where
└── service                 where to send requests: the address            ┘

The split is deliberate. types, message and portType describe what the service does, in the abstract. binding and service describe how and where to call it. The same operations can have several bindings, one per protocol, which is how this service offers both SOAP 1.1 and 1.2. Read them in that order.

1. types: the shape of every message

WSDL: types
<wsdl:types>
  <s:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/">
    <s:element name="Add"><s:complexType><s:sequence>
      <s:element minOccurs="1" maxOccurs="1" name="intA" type="s:int"/>
      <s:element minOccurs="1" maxOccurs="1" name="intB" type="s:int"/>
    </s:sequence></s:complexType></s:element>
    <s:element name="AddResponse"><s:complexType><s:sequence>
      <s:element minOccurs="1" maxOccurs="1" name="AddResult" type="s:int"/>
    </s:sequence></s:complexType></s:element>
    ...
  </s:schema>
</wsdl:types>

The types section contains an XML Schema, the standard language for describing XML documents. For Add, it says: an element called Add contains a sequence of exactly two elements, intA then intB, each appearing once (minOccurs="1" maxOccurs="1") and each an int, XML Schema's 32-bit integer. The response is an AddResponse holding one AddResult.

This is the most useful part for a tester. It says which fields are required, their types, their order, and how many times each may appear. Every one of those is a test case: leave out intB, send five, send a number too big for 32 bits. Lesson 7 also uses this schema to check every response automatically.

2 and 3. message and portType: the operations

WSDL: messages and port type
<wsdl:message name="AddSoapIn"><wsdl:part name="parameters" element="tns:Add"/></wsdl:message>
<wsdl:message name="AddSoapOut"><wsdl:part name="parameters" element="tns:AddResponse"/></wsdl:message>

<wsdl:portType name="CalculatorSoap">
  <wsdl:operation name="Add"><wsdl:input message="tns:AddSoapIn"/><wsdl:output message="tns:AddSoapOut"/></wsdl:operation>
  ...
</wsdl:portType>

A message names a piece of data that goes in or out, pointing at an element from the types. The portType is the list of operations, the service's interface: Add takes an AddSoapIn message and returns an AddSoapOut. Following the references: the input is the Add element, the output the AddResponse element. A portType is like an interface in Java: names and signatures, nothing about the wire.

4. binding: how to call it

WSDL: bindings
<wsdl:binding name="CalculatorSoap" type="tns:CalculatorSoap">
  <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
  <wsdl:operation name="Add">
    <soap:operation soapAction="http://tempuri.org/Add" style="document"/>
    <wsdl:input><soap:body use="literal"/></wsdl:input>
    <wsdl:output><soap:body use="literal"/></wsdl:output>
  </wsdl:operation>
  ...
</wsdl:binding>
<wsdl:binding name="CalculatorSoap12" type="tns:CalculatorSoap">
  <soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/>
  ...
</wsdl:binding>
  • soap:binding and soap12:binding say which SOAP version each binding uses; the prefixes point to different namespaces. transport says the messages go over HTTP.
  • soapAction is the value for the SOAPAction header in SOAP 1.1, or the action parameter in SOAP 1.2.
  • style="document" and use="literal" mean the Body contains exactly the element the schema describes, nothing added. The older alternatives, rpc style and encodeduse, add their own wrapping and type information; you'll still meet them in old services, and they're a common source of interoperability problems.

5. service: where to send requests

WSDL: service
<wsdl:service name="Calculator">
  <wsdl:port name="CalculatorSoap" binding="tns:CalculatorSoap">
    <soap:address location="https://api.testingapis.com/api/soap/calculator"/>
  </wsdl:port>
  <wsdl:port name="CalculatorSoap12" binding="tns:CalculatorSoap12">
    <soap12:address location="https://api.testingapis.com/api/soap/calculator"/>
  </wsdl:port>
</wsdl:service>
This example uses endpoints for signed-in members.Sign inCreate a free account

Each port joins a binding to an address. Both bindings here use the same URL, and the Content-Type tells the server which version arrived. Check this address in your tests. Services behind proxies and load balancers often build it from the request they received, and publish an internal hostname or http instead of https. Generated clients then send every call to the wrong place.

Testing what the WSDL promises

A WSDL is XML, so XPath can check it like any other response. The project's WsdlTest fetches it once and checks each part:

WsdlTest.java
@Test
@DisplayName("the port type lists the operations")
void operations() {
    assertEquals(List.of("Add", "Subtract", "Multiply", "Divide"),
            q.strings(wsdl, "/wsdl:definitions/wsdl:portType/wsdl:operation/@name"));
}
WsdlTest.java
@Test
@DisplayName("the types section says what each operation takes and returns")
void messageTypes() {
    String add = "/wsdl:definitions/wsdl:types/s:schema/s:element[@name='Add']//s:element";
    assertEquals(List.of("intA", "intB"), q.strings(wsdl, add + "/@name"));
    assertEquals(List.of("s:int", "s:int"), q.strings(wsdl, add + "/@type"));
    assertEquals(List.of("1", "1"), q.strings(wsdl, add + "/@minOccurs")); // both required

    assertEquals("AddResult", q.string(wsdl,
            "/wsdl:definitions/wsdl:types/s:schema/s:element[@name='AddResponse']//s:element/@name"));
}
WsdlTest.java
@Test
@DisplayName("the SOAP 1.1 binding gives each operation's SOAPAction")
void soapActions() {
    String binding = "/wsdl:definitions/wsdl:binding[@name='CalculatorSoap']";
    assertEquals("http://tempuri.org/Add",
            q.string(wsdl, binding + "/wsdl:operation[@name='Add']/soap:operation/@soapAction"));
    assertEquals("document", q.string(wsdl, binding + "/wsdl:operation[@name='Add']/soap:operation/@style"));
}
WsdlTest.java
@Test
@DisplayName("the service section says where to send requests")
void address() {
    assertEquals(Api.BASE_URL + "/api/soap/calculator",
            q.string(wsdl, "/wsdl:definitions/wsdl:service/wsdl:port[@name='CalculatorSoap']/soap:address/@location"));
}
This example uses endpoints for signed-in members.Sign inCreate a free account

Each query names the exact part it means, such as the binding called CalculatorSoap or the schema element named Add, using the predicates in square brackets that lesson 6 explains. Tests like these catch changes to the contract: an operation renamed or removed, a field that became optional, an address that points somewhere else. Clients generated from the old WSDL would break on each of those, so they deserve a failing test before release.

PartWhat to check
typesRequired fields, types and limits match the documentation and the real behavior
portTypeEvery documented operation is there, and nothing unexpected
bindingThe SOAP versions offered, and a SOAPAction for each operation
serviceThe address is the public HTTPS URL, not an internal one
Against the serviceThe service actually behaves as described: lesson 7

Check your understanding

  1. 1.Which part of a WSDL says where to send requests?Show answer

    The service section: each port has an address with a location.

  2. 2.Which parts describe what the service does, without any protocol details?Show answer

    types, message and portType. The binding and service add the how and where.

  3. 3.How can one WSDL describe both SOAP 1.1 and SOAP 1.2?Show answer

    With two bindings for the same portType, one using soap:binding and one soap12:binding, each with its own port.

  4. 4.The schema says minOccurs="1" for intB. What test does that suggest?Show answer

    Send a request without intB and check the service returns a fault rather than a result.

  5. 5.Why check the address in the WSDL?Show answer

    Clients generated from the WSDL send requests to that address. Services behind proxies sometimes publish an internal or http address by mistake.

Exercise

Add tests to WsdlTest for the SOAP 1.2 binding: that it exists, that it uses the soap12 namespace http://schemas.xmlsoap.org/wsdl/soap12/, and that it has the same four operations as the 1.1 binding. Then write one that fails if any operation in the port type is missing from either binding.

Practise and go further