Architecture

MockWebServiceMessageSender

MockWebServiceMessageSender is the entry-point to Spring WS Test. If it is used instead of standard WebServiceMessageSender all requests will be processed by the library.

RequestProcessor

Request processor is a basic building block of the library. There are two basic types of ReqestProcessors - request validators and response generators. This division is logical only. Both groups implement the same interface. When a request processor is called with a request, there are three possible outcomes.

  1. null - if a request processor returns null, next processor from the list is called. If the last processor returns null, an exception is thrown.
  2. a response - if a request processor returns a response, processing is terminated and the response is returned from MockWebServiceMessageSender
  3. an exception - if an exception is thrown, processing is terminated and the same exception is re-thrown from the message sender. Validators throw WsTestException if the validation does not succeed.

Configuration

Some parts of the library are complex. The complexity is not arbitrary, it's caused by the number of configuration settings. To make your life easier, most common configuration options are encapsulated in a custom tag library. For majority of scenarios provided custom tag library should suffice. The rest can use standard configuration which provides more flexibility.

Schema based configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:swst="http://javacrumbs.net/schema/1.0/spring-ws-test"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://javacrumbs.net/schema/1.0/spring-ws-test http://javacrumbs.net/schema/1.0/spring-ws-test.xsd">
                
        <swst:mock-ws-message-sender id="mock-sender" autowireRequestProcessors="true" autoinjectMock="true">
                <swst:namespaces>
                        <entry key="ns" value="http://www.springframework.org/spring-ws/samples/airline/schemas/messages"/>
                </swst:namespaces>
                <swst:resource-config pathPrefix="mock-xml/" prependUri="false" templateProcessor="FreeMarker">
                        <swst:discriminators>
                                <entry key="getFlightsRequest" value="//ns:from;//ns:to"/>
                        </swst:discriminators>          
                </swst:resource-config>
                <swst:schemas>
                        xml/schema.xsd
            </swst:schemas>
        </swst:mock-ws-message-sender>
</beans>

Schema based configuration are explained in the Quick start documentation. Here we will cover only advanced features.

  • autowireRequestProcessors - if set to true RequestProcessors are looked up in the ApplicationContext and automatically used by MockWebServiceMessageSender. Default value is true.
  • autoinjectMock - if set to true MockWebServiceMessageSender is injected into all WebServiceTemplates found in the ApplicationContext. Default is true.
  • pathPrefix defines in which directory will be the mock responses and control request stored. Default value is "mock-xml".
  • prependUri - if set to true, hostname from service uri is prepended before name of the file. For example path "mock-xml/www.aerlingus.com/GetFlightsRequest/PRG-DUB-response.xml" will be used. Default value is false.
  • templateProcessor - sets template processor. Default value is XSLT, FreeMarker is the only other possible option.
  • schemas element contains whitespace separated list of schemas, that are used for request validation. This element is optional.

Basic components

In this part list of basic components and their configuration options will be described.

ExpressionResolver

Resolves expressions. It's used in various parts of the library. Default implementation is XPathExpressionResolver. In order to use XPathExpression resolver, it's necessary to configure namespace prefixes.

<bean id="xpathResolver" class="net.javacrumbs.springws.test.expression.XPathExpressionResolver">
        <property name="namespaceMap">
                <map>
                        <entry key="soapenv" value="http://schemas.xmlsoap.org/soap/envelope/" />
                        <entry key="ns" value="http://www.springframework.org/spring-ws/samples/airline/schemas/messages" />
                </map>
        </property>     
</bean>

ResourceLookup

Resource lookup looks up a resource based on the request, uri and context attributes. It has two basic implementation.

  • PayloadRootBasedResourceLookup looks up resource based on request payload, path prefix, suffix and other parameters. this lookup is used by schema based config for both control request and mock response lookup.
  • ExpressionBasedResourceLookup does lookup based on custom XPath expression.

    Both variants require expression resolver to be set.

    Both implementations also use a TemplateProcessor which processes a template. By default XsltTemplateProcessor is used, so a XSLT template can be used in all resources.

DefaultResponseGenerator

Generates mock response based on provided resource lookup. If the resource is not found, returns null. It's possible to include SOAP elements to the response but it's also possible to specify only payload. In this case SOAP envelope will be automatically generated.

<bean class="net.javacrumbs.springws.test.generator.DefaultResponseGenerator">
        <property name="resourceLookup">
                <bean class="net.javacrumbs.springws.test.lookup.ExpressionBasedResourceLookup">
                        <property name="resourceExpressions">
                                <list>
                                        <value>concat('mock-responses/',$uri.host, '/', local-name(//soapenv:Body/*[1]),'/',//ns:from,'-',//ns:to,'-response.xml')</value>
                                        <value>concat('mock-responses/',$uri.host, '/', local-name(//soapenv:Body/*[1]),'/default-response.xml')</value>
                                </list>
                        </property>
                        <property name="expressionResolver" ref="xpathResolver"/>
                </bean>
        </property>
</bean> 

XmlCompareRequestValidator

It's similar to the default request generator, the difference is that instead of generating responses it validates the request. You can use XSLT templates here as well. You can also specify special placeholder $IGNORE for values that should be ignored. It is also possible to define only payload in the control file. In such case only payloads will be compared and SOAP envelope will be ignored.

<bean class="net.javacrumbs.springws.test.validator.XmlCompareRequestValidator">
        <property name="controlResourceLookup">
                <bean class="net.javacrumbs.springws.test.lookup.ExpressionBasedResourceLookup">
                        <property name="resourceExpressions">
                                <list>
                                        <value>concat('mock-responses/',$uri.host, '/', local-name(//soapenv:Body/*[1]),'/',//ns:from,'-',//ns:to,'-request.xml')</value>
                                </list>
                        </property>
                </bean>
        </property>             
</bean>         

XPathRequestValidator

It is also possible to validate request using XPath. For example you can use following configuration.

<bean class="net.javacrumbs.springws.test.validator.XPathRequestValidator">
        <property name="expressionResolver" ref="xpathResolver"/>
        <property name="exceptionMapping">
                <map>
                        <entry key="//ns:serviceClass != 'economy' and //ns:serviceClass != 'business'" value="Unsupported service class"/>
                </map>
        </property>
</bean>

Validator configured in this way will throw an exception if service class is not "economy" or "business".

WebServiceTransportExceptionGenerator

Similar to XPathRequestValidator is WebServiceTransportExceptionGenerator. The only difference is the exception thrown. WebServiceTransportException is throw so this class can be used to simulate connection problems.

Test context

Test context is useful, when you need to use some specific configuration. For example you want to set expected departure time and use it in the response.

WsTestContextHolder.getTestContext().setAttribute("departureTime", "2009-07-14");

You can set the attribute in the test method and then use it

  • In the XSLT template as a template parameter.
  • In the XPath using $context.departureTime
  • In the FreeMarker template as a variable.
  • Programatically

    It's a good idea to clear the test context at the tearDown() method.

XPath evaluation

In this part we will describe possibilities of the XPath evaluation.

Let's imagine that service URL is "http://www.csa.cz" and that following request was used

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
        <SOAP-ENV:Header />
        <SOAP-ENV:Body>
                <ns3:GetFlightsRequest
                        xmlns:ns2="http://www.springframework.org/spring-ws/samples/airline/schemas/types"
                        xmlns:ns3="http://www.springframework.org/spring-ws/samples/airline/schemas/messages">
                        <ns3:from>PRG</ns3:from>
                        <ns3:to>DUB</ns3:to>
                        <ns3:departureDate>2059-07-27</ns3:departureDate>
                        <ns3:serviceClass>economy</ns3:serviceClass>
                </ns3:GetFlightsRequest>
        </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Also assume that WsTestContext was set like this

WsTestContextHolder.getTestContext().setAttribute("testName", "test136");

Then following results will correspond to XPath expressions: XPathEval

XPath expressionResult
concat(local-name(//soapenv:Body/*[1]),'-response.xml')GetFlightsRequest-response.xml
concat(local-name(//soapenv:Body/*[1]),'/default-response.xml')GetFlightsRequest/default-response.xml
concat(local-name(//soapenv:Body/*[1]),'/',//ns:from,'-',//ns:to,'-response.xml')GetFlightsRequest/PRG-DUB-response.xml
concat($uri.host, '/', local-name(//soapenv:Body/*[1]),'/',//ns:from,'-',//ns:to,'-response.xml')www.csa.cz/GetFlightsRequest/PRG-DUB-response.xml
concat($context.testName,'-response.xml')test136-response.xml

Test context

Test context can be used for more fine grained test configuration.

In you test code you can specify a context attribute like this.

WsTestContextHolder.getTestContext().setAttribute("testName", "test136");

Later on you can either retrieve it programmatically like this

WsTestContextHolder.getTestContext().getAttribute("testName")  

You can also access it in XPath like this

concat($context.testName,'-response.xml')  

or it is automatically injected as XSLT variable, so it can be accessed like this

<?xml version="1.0" encoding="windows-1250"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
        <xsl:param name="number"/>

        <xsl:template match="/">
                <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
                        <soapenv:Header />
                        <soapenv:Body>
                                <ns:test xmlns:ns="http://www.example.org/schema">
                                        <ns:number>2</ns:number>
                                        <ns:text><xsl:value-of select="$testName"/></ns:text>
                                </ns:test>
                        </soapenv:Body>
                </soapenv:Envelope>
        </xsl:template>
</xsl:stylesheet>

Template processors

Sometimes it useful to specify both requests and responses using templates. This way it's possible to use one file for multiple tests. By default XSLT template processor is used.

XSLT template

XSLT template uses the request as input. It's also possible to use context variables. A template can look like the following example. Please note how context variable is used for the departure time and value from the request for service class.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
        <xsl:param name="departureTime"/>
        <xsl:template match="/">
                <GetFlightsResponse
                        xmlns="http://www.springframework.org/spring-ws/samples/airline/schemas/messages"
                        xmlns:msg="http://www.springframework.org/spring-ws/samples/airline/schemas/messages"
                        xmlns:tps="http://www.springframework.org/spring-ws/samples/airline/schemas/types">
                        <flight>
                                <tps:number>OK0651</tps:number>
                                <tps:departureTime><xsl:value-of select="$departureTime"/></tps:departureTime>
                                <tps:from>
                                        <tps:code>PRG</tps:code>
                                        <tps:name>Prague</tps:name>
                                        <tps:city>Prague</tps:city>
                                </tps:from>
                                <tps:arrivalTime>2001-12-31T12:00:00
                                </tps:arrivalTime>
                                <tps:to>
                                        <tps:code>CDG</tps:code>
                                        <tps:name>CDG</tps:name>
                                        <tps:city>Paris</tps:city>
                                </tps:to>
                                <tps:serviceClass><xsl:value-of select="//msg:serviceClass"/></tps:serviceClass>
                        </flight>
                </GetFlightsResponse>
        </xsl:template>
</xsl:stylesheet>

FreeMarker template

It's also possible to use a FreeMarker template. In this case you have to set "templateProcessor" attribute of "resource-config" element to "FreeMarker". Alternatively, it's possible to set templateProcessor property on resource lookup bean, if you are not using simplified configuration. A FreeMarker template could look like the following one.

<#ftl ns_prefixes={"msg":"http://www.springframework.org/spring-ws/samples/airline/schemas/messages"}>
<GetFlightsResponse
        xmlns="http://www.springframework.org/spring-ws/samples/airline/schemas/messages"
        xmlns:msg="http://www.springframework.org/spring-ws/samples/airline/schemas/messages"
        xmlns:tps="http://www.springframework.org/spring-ws/samples/airline/schemas/types">
        <flight>
                <tps:number>OK0651</tps:number>
                <tps:departureTime>${departureTime}</tps:departureTime>
                <tps:from>
                        <tps:code>PRG</tps:code>
                        <tps:name>Prague</tps:name>
                        <tps:city>Prague</tps:city>
                </tps:from>
                <tps:arrivalTime>2001-12-31T12:00:00
                </tps:arrivalTime>
                <tps:to>
                        <tps:code>CDG</tps:code>
                        <tps:name>CDG</tps:name>
                        <tps:city>Paris</tps:city>
                </tps:to>
                <tps:serviceClass>${request["//msg:serviceClass"]}</tps:serviceClass>
        </flight>
</GetFlightsResponse>

Default order when autowiring is used

When RequestProcessor autowiring is used, they are by default invoked in the following order.

ClassOrder
XmlCompareRequestValidator10
SchemaRequestValidator20
XPathRequestValidator30
WebServiceTransportExceptionGenerator40
DefaultResponseGenerator50

Logging

Spring WS Test log some information using commons-logging. For example, if you need to troubleshoot your test and you are using log4j, you can add following line into your log4j.properties

log4j.logger.net.javacrumbs=TRACE