March 04, 2010

SoapUI limitations and workarounds : mock response test step

« SOAP web service testing with soapUI | Main | Definition Builder mit Java Beispiel »

The following example describes one limitation of soapUI and a possible workaround. It concerns the way mock services are used in mock response test steps.

We consider a web service (TripPriceService) that offers an operation to compute the price of a trip composed of many flights. This web service uses another web service (FlightPriceService), that returns the price of a single flight between 2 locations.

Formula : price = adults * sum(getFlightPrice(from[i], to[i]))

Here are the WSDLs of the 2 services:

TripPriceService.wsdl

<?xml version='1.0' encoding='UTF-8'?>
<wsdl:definitions name="TripPriceServiceFacadeService"
  targetNamespace="http://trip.price.service" xmlns:ns1="http://cxf.apache.org/bindings/xformat"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://trip.price.service"
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <wsdl:types>
    <xs:schema attributeFormDefault="unqualified"
      elementFormDefault="unqualified" targetNamespace="http://trip.price.service"
      xmlns:tns="http://trip.price.service" xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:element name="getCompleteFlightPrice" type="tns:getCompleteFlightPrice" />
      <xs:element name="getCompleteFlightPriceResponse" type="tns:getCompleteFlightPriceResponse" />
      <xs:complexType name="getCompleteFlightPrice">
        <xs:sequence>
          <xs:element minOccurs="0" name="trip" type="tns:completeTrip" />
        </xs:sequence>
      </xs:complexType>
      <xs:complexType name="completeTrip">
        <xs:sequence>
          <xs:element name="adults" type="xs:int" />
          <xs:element maxOccurs="unbounded" minOccurs="0"
            name="trips" nillable="true" type="tns:tripPart" />
        </xs:sequence>
      </xs:complexType>
      <xs:complexType name="tripPart">
        <xs:sequence>
          <xs:element minOccurs="0" name="from" type="xs:string" />
          <xs:element minOccurs="0" name="to" type="xs:string" />
        </xs:sequence>
      </xs:complexType>
      <xs:complexType name="getCompleteFlightPriceResponse">
        <xs:sequence>
          <xs:element name="return" type="xs:float" />
        </xs:sequence>
      </xs:complexType>
      <xs:element name="TripPriceServiceException" type="tns:TripPriceServiceException" />
      <xs:complexType name="TripPriceServiceException">
        <xs:sequence />
      </xs:complexType>
    </xs:schema>
  </wsdl:types>
  <wsdl:message name="TripPriceServiceException">
    <wsdl:part element="tns:TripPriceServiceException" name="TripPriceServiceException">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getCompleteFlightPriceResponse">
    <wsdl:part element="tns:getCompleteFlightPriceResponse"
      name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getCompleteFlightPrice">
    <wsdl:part element="tns:getCompleteFlightPrice" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:portType name="ITripPriceServiceFacade">
    <wsdl:operation name="getCompleteFlightPrice">
      <wsdl:input message="tns:getCompleteFlightPrice" name="getCompleteFlightPrice">
      </wsdl:input>
      <wsdl:output message="tns:getCompleteFlightPriceResponse"
        name="getCompleteFlightPriceResponse">
      </wsdl:output>
      <wsdl:fault message="tns:TripPriceServiceException"
        name="TripPriceServiceException">
      </wsdl:fault>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="TripPriceServiceFacadeServiceSoapBinding"
    type="tns:ITripPriceServiceFacade">
    <soap:binding style="document"
      transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="getCompleteFlightPrice">
      <soap:operation soapAction="" style="document" />
      <wsdl:input name="getCompleteFlightPrice">
        <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output name="getCompleteFlightPriceResponse">
        <soap:body use="literal" />
      </wsdl:output>
      <wsdl:fault name="TripPriceServiceException">
        <soap:fault name="TripPriceServiceException" use="literal" />
      </wsdl:fault>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="TripPriceServiceFacadeService">
    <wsdl:port binding="tns:TripPriceServiceFacadeServiceSoapBinding"
      name="TripPriceServiceFacadePort">
      <soap:address
        location="http://localhost:8080/complete-trip-price-0.0.1-SNAPSHOT/webservices/TripPriceService" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

FlightPriceService.wsdl

<?xml version='1.0' encoding='UTF-8'?>
<wsdl:definitions name="FlightPriceServiceFacadeService"
  targetNamespace="http://external.services/flight" xmlns:ns1="http://cxf.apache.org/bindings/xformat"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://external.services/flight"
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <wsdl:types>
    <xs:schema attributeFormDefault="unqualified"
      elementFormDefault="unqualified" targetNamespace="http://external.services/flight"
      xmlns:tns="http://external.services/flight" xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:element name="getFlightPrice" type="tns:getFlightPrice" />
      <xs:element name="getFlightPriceResponse" type="tns:getFlightPriceResponse" />
      <xs:complexType name="getFlightPrice">
        <xs:sequence>
          <xs:element minOccurs="0" name="from" type="xs:string" />
          <xs:element minOccurs="0" name="to" type="xs:string" />
        </xs:sequence>
      </xs:complexType>
      <xs:complexType name="getFlightPriceResponse">
        <xs:sequence>
          <xs:element name="return" type="xs:float" />
        </xs:sequence>
      </xs:complexType>
      <xs:element name="LocationNotFoundException" type="tns:LocationNotFoundException" />
      <xs:complexType name="LocationNotFoundException">
        <xs:sequence />
      </xs:complexType>
    </xs:schema>
  </wsdl:types>
  <wsdl:message name="LocationNotFoundException">
    <wsdl:part element="tns:LocationNotFoundException" name="LocationNotFoundException">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getFlightPrice">
    <wsdl:part element="tns:getFlightPrice" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getFlightPriceResponse">
    <wsdl:part element="tns:getFlightPriceResponse" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:portType name="IFlightPriceServiceFacade">
    <wsdl:operation name="getFlightPrice">
      <wsdl:input message="tns:getFlightPrice" name="getFlightPrice">
      </wsdl:input>
      <wsdl:output message="tns:getFlightPriceResponse"
        name="getFlightPriceResponse">
      </wsdl:output>
      <wsdl:fault message="tns:LocationNotFoundException"
        name="LocationNotFoundException">
      </wsdl:fault>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="FlightPriceServiceFacadeServiceSoapBinding"
    type="tns:IFlightPriceServiceFacade">
    <soap:binding style="document"
      transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="getFlightPrice">
      <soap:operation soapAction="" style="document" />
      <wsdl:input name="getFlightPrice">
        <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output name="getFlightPriceResponse">
        <soap:body use="literal" />
      </wsdl:output>
      <wsdl:fault name="LocationNotFoundException">
        <soap:fault name="LocationNotFoundException" use="literal" />
      </wsdl:fault>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="FlightPriceServiceFacadeService">
    <wsdl:port binding="tns:FlightPriceServiceFacadeServiceSoapBinding"
      name="FlightPriceServiceFacadePort">
      <soap:address
        location="http://localhost:8088/external-services-0.0.1-SNAPSHOT/webservices/FlightPriceService" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

We imagine one adult wants to fly from Berlin to Paris and then from Paris to London. The flight from Berlin to Paris costs 15.0 Euro and the flight from Paris to London 100.0 Euro. So we expect the web service to return 115.0.

Let us create the test case in soapUI.

1. Create a new project with all the interfaces

  • Select "File" -> "New soapUI Project"
  • Set the project name: complete-trip-price
  • Set the initial WSDL: http://localhost:8080/complete-trip-price-0.0.1-SNAPSHOT/webservices/TripPriceService?wsdl
  • Click "OK"
  • Right click the project and select "Add WSDL"
  • Set the WSDL location: /home/ubuntu/workspace/test-webservice-soapui/complete-trip-price/complete-trip-price-service/src/main/resources/wsdl/FlightPriceService.wsdl

2. Generate a test suite and a test case for the getCompleteFlightPrice operation

  • Right click the "TripPriceServiceFacadeServiceSoapBinding" and select "Generate TestSuite"
  • Click "OK"
  • Enter a name for the test suite: TripPriceServiceFacadeServiceSoapBinding TestSuite
  • Click "OK"
  • Set a timeout for the test case

3. Configure the test request

  • Edit the test request content
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:trip="http://trip.price.service">
   <soapenv:Header/>
   <soapenv:Body>
      <trip:getCompleteFlightPrice>
         <trip>
            <adults>1</>
            <trips>
               <from>Berlin</from>
               <to>Paris</to>
            </trips>
            <trips>
               <from>Paris</from>
               <to>London</to>
            </trips>
         </trip>
      </trip:getCompleteFlightPrice>
   </soapenv:Body>
</soapenv:Envelope>
  • Add an "XPath Match" assertion to check the price

XPath Expression

declare namespace soap='http://schemas.xmlsoap.org/soap/envelope/';
declare namespace ns1='http://trip.price.service';
//ns1:getCompleteFlightPriceResponse/return

Expected Result

115.0

4. Configure the mock responses

  • Append a mock response test step
  • Specify a name for it, for eample "getFlightPrice Berlin-Paris Mock Response"
  • Select the getFlightPrice operation of the "FlightPriceServiceFacadeServiceSoapBinding" interface
  • Set the port to listen on 8088
  • Set the path to listen on /external-services-0.0.1-SNAPSHOT/webservices/FlightPriceService
  • Click "OK"
  • Edit the content of the response to return 15.0
  • Set the start step of the mock response : getCompleteFlightPrice
  • Append a mock response test step
  • Specify a name for it, for eample "getFlightPrice Paris-London Mock Response"
  • Select the getFlightPrice operation of the "FlightPriceServiceFacadeServiceSoapBinding" interface
  • Set the port to listen on 8088
  • Set the path to listen on /external-services-0.0.1-SNAPSHOT/webservices/FlightPriceService
  • Click "OK"
  • Edit the content of the response to return 100.0
  • Set the start step of the mock response : getCompleteFlightPrice

5. Run the test case

  • Click the "Runs this testcase" button

The test case is failed, because of the XPath Match assertion. The web service returns 30.0 instead of 115.0.

In fact our first mock response test step has answered twice.


Delete the 2 mock response test steps from the test case... we will try something else.


6. Workaround

A possible workaround is to create a mock service with a sequence of responses and to start it via groovy script when launching the test case.

6.1 Create a mock service

  • Right click the "FlightPriceServiceFacadeServiceSoapBinding" interface and select "Generate MockService"
  • Set the port to listen on 8088
  • Set the path to listen on /external-services-0.0.1-SNAPSHOT/webservices/FlightPriceService
  • Click "OK"
  • Specify the name of the mock service, for example "FlightPriceService MockService" and click "OK"
  • Edit the generated mock response to return 15.0
  • Rename the mock response to "MockResponse Berlin-Paris"
  • Right click the getFlightPrice operation of the mock service and select "New MockResponse"
  • Enter a name for the mock response, for example "MockResponse Paris-London" and click "OK"
  • Edit the generated mock response to return 100.0
  • Check the dispatch style of the operation is sequence

6.2 Start the mock service in a setup script

  • Double click the test case and click the "Setup Script" tab
  • Enter the following script
def project = testCase.getTestSuite().getProject();
def mockService = project.getMockServiceByName("FlightPriceService MockService");
mockService.start();

6.3 Stop the mock service in a teardown script

  • Click the "TearDown Script" tab
  • Enter the following script
def project = testCase.getTestSuite().getProject();
def mockService = project.getMockServiceByName("FlightPriceService MockService");
def mockRunner = mockService.getMockRunner();
mockRunner.stop();

Now run you test case again, it should be successful.


Technorati Tags:

Posted by nadege.griesser at 12:17 PM in Testing

 

[Trackback URL for this entry]

Comment: Shivprasad at Fr, 4 Jun 8:51 AM

Thank you nadege for the article.

I am novice user of soapUI.
The description of mock services in Getting Started Guide on soapUI website (www.soapui.org) is confusing, specifically step 6 onwards in '4) Customizing a MockResponse'.
Please enlighten the meaning of these steps.

Comment: Shivprasad at Fr, 4 Jun 8:54 AM

Thank you for nice explanation.

I am novice user of soapUI.
Description of MockServices in Getting Started guide, specifically step 6 onwards in '4) Customizing a MockService' is confusing.
I request you to please enlighten the meaning of these steps, preferably with a small example.

Comment: Nadege at Mi, 9 Jun 4:00 PM

I think some usefull screenshots are missing after step 8 and I cannot really understand what the tutorial should do.

Unfortunately the QUERY_MATCH dispatch type does not work with my version of soapUI.

All I could do is try the XPATH dispatch type. I added a MockResponse called "AFA" to the MockService.

<soap:Body>
<web:ConversionRateResponse> <web:ConversionRateResult>666&lt/web:ConversionRateResult>
</web:ConversionRateResponse>
</soap:Body>

and set the dispatch type to "XPATH" and the expression to :
declare namespace web='http://www.webserviceX.NET/';
//web:ConversionRate[1]/web:FromCurrency[1]


Then I sent 2 different requests to the MockService : one with FromCurrency AFA and one with something else.

The request with FromCurrency AFA gets 666 as a response and the other one the default response.

In fact, the XPATH expression selects the name of the MockResponse to return.


Maybe you can have a look at think link, which explains dispatching.
http://www.soapui.org/Service-Mocking/simulating-complex-behaviour.html#Query/Match_Dispatching

Comment: shiva at Mo, 29 Aug 6:33 AM

I am beginner to SOAP UI, instead of using the test case in the SOAP. Can i write the test cases in other languages like perl,python...etc.
1. I created a server
2. I created client in perl/python and test the server in SOAP UI.....Is this possible...
Please any body help me.

If anybody knows which client languages does the SOAPUI works..?

Comment: ashiskh at Mi, 8 Feb 2:34 PM

It works on Java

Your comment:

(not displayed)
 
 
 

Live Comment Preview: