Client Test Tutorial

This page provides a step-by-step tutorial of WS client testing. In the tutorial we will test a simple calculator web service. The source code of the sample application is downloadable from SVN repository.

It's possible to download it using following command svn co https://java-crumbs.svn.sourceforge.net/svnroot/java-crumbs/simple-client-test/tags/simple-client-test-0.22/simple-client-test/ simple-client-test

Tested application

Let's start with the XSD defining data structures of the service.

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://javacrumbs.net/calc"
        xmlns:tns="http://javacrumbs.net/calc" elementFormDefault="qualified">

        <element name="plusRequest">
                <complexType>
                        <sequence>
                                <element name="a" type="int" />
                                <element name="b" type="int" />
                        </sequence>
                </complexType>
        </element>

        <element name="plusResponse">
                <complexType>
                        <sequence>
                                <element name="result" type="int" />
                        </sequence>
                </complexType>
        </element>
</schema>

There is one "operation" called plus allowing to add two numbers. The result is returned in the result element.

Spring configuration (client-config.xml) is simple too.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        
        <bean class="net.javacrumbs.calc.Calc"/>
        
        <bean id="wsTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
                <property name="defaultUri" value="http://localhost/calc"/>
                <property name="marshaller" ref="marshaller"/>
                <property name="unmarshaller" ref="marshaller"/>
        </bean> 
        
        <bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
                <property name="contextPath" value="net.javacrumbs.calc.model"/>
        </bean>
</beans>

The only noteworthy part is the marshaller. JAXB2 is used and corresponding classes are generated by Maven using following configuration.

The client code is also simple, we just have to create request payload, send it to WS template and extract the response.

public class Calc {
        @Autowired
        private WebServiceOperations wsTemplate;
        
        public int plus(int a, int b)
        {
                PlusRequest request = new PlusRequest();
                request.setA(a);
                request.setB(b);
                PlusResponse result = (PlusResponse) wsTemplate.marshalSendAndReceive(request);
                return result.getResult();
        }
}

The Test

Now when the code to be tested is clear, we can focus on the test itself. Let's start with the easiest one

Response Generation

@RunWith(SpringJUnit4ClassRunner.class)

//load your standard config
@ContextConfiguration(locations={"classpath:client-config.xml"})

//Add the listener (DirtiesContextTestExecutionListener.class,  TransactionalTestExecutionListener.class might be needed if @DirtiesContext or @Transactional is used.
@TestExecutionListeners({WsMockControlTestExecutionListener.class, DependencyInjectionTestExecutionListener.class})

public class CalcTest {
    @Autowired
    private Calc calc;
    
    //inject mock control
    @Autowired
    private WsMockControl mockControl;

        @Test
        public void testSimple()
        {
                mockControl.returnResponse("response1.xml");

                int result = calc.plus(1, 2);
                assertEquals(3, result);
                
                mockControl.verify();
        }
}

The test class starts with standard Spring JUnit support. The TestExecutionListener configuration section is the important bit. In addition to standard Spring listeners, WsMockControlTestExecutionListener.class is used. The listener does all the magic - it makes WsMockControl bean available, alters WsTemplate so no server is called and in general, it makes sure that everything works. Please note that DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class might be needed if @DirtiesContext or @Transactional is used.

Once we have WsMockControl, we can write the test. First of all, it's necessary to define which response should be returned from the web service call. That's achieved by mockControl.returnResponse("response1.xml"); call. When we have this it's possible to call the WS client code. The mock infrastructure kicks in and instead of calling remote service, response1.xml is returned.

It's recommended to verify, that the web service was indeed called. It's done by mockControl.verify();

Request Validation

Usually it's useful to verify that the request generated by the client code has expected form. It's easy to achieve this way.

@Test
public void testVerifyRequest()
{
        mockControl.expectRequest("request1.xml").returnResponse("response1.xml");
        
        int result = calc.plus(1, 2);
        assertEquals(3, result);
        
        mockControl.verify();
}

Please note that it's possible to define the requests and responses either as full soap messages or as payload only. It depends only on your preference.

Schema Validation

We can also make sure the request conforms to XSD.

@Test
public void testSchema()
{
        mockControl.validateSchema("xsd/calc.xsd").expectRequest("request1.xml").returnResponse("response1.xml");
        
        int result = calc.plus(1, 2);
        assertEquals(3, result);
        
        mockControl.verify();
}  

Ignoring Request Values

Even though we could define special request and response file for every possible test it would soon become a maintenance nightmare. Therefore there are few mechanisms that allow us to reuse request and response files. The first one is the $IGNORE placeholder.

It's possible to ignore some parts of the request when comparing it to generated value.

<plusRequest xmlns="http://javacrumbs.net/calc">
  <a>${IGNORE}</a>
  <b>${IGNORE}</b>
</plusRequest>

This "template" can be reused for all possible tests. Values of elements a and b are ignored.

Response Templates

Similar mechanism can be used for responses too. In response, it's not possible just to simply ignore some values, we have to generate them. One way it is to use a XSLT template.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
        <xsl:template match="/">
                        <plusResponse xmlns="http://javacrumbs.net/calc" xmlns:c="http://javacrumbs.net/calc">
                          <result><xsl:value-of select="//c:a + //c:b"/></result>
                        </plusResponse>
                </xsl:template>
</xsl:stylesheet>

This template takes value from requests and calculates the response. Here we have in fact implemented the service using the template. In more real-life like scenarions it's useful too. It's possible to generate the response based on the request values. Even though XSLT is quite powerful toll, FreeMarker templates are supported too. You just have to call mockControl.useFreeMarkerTemplateProcessor().

Context Variables

Context Variables is another tool how to parametrize the templates. It's possible to define variables in the test script and use them in the template. Context Variables can be used both in request and response templates.

        @Test
        public void testContext()
        {
                mockControl
                        .setTestContextAttribute("a", 1)
                        .setTestContextAttribute("b", 4)
                        .useFreeMarkerTemplateProcessor()
                        .expectRequest("request-context.xml")
                        .returnResponse("response2.xml");
                
                int result = calc.plus(1, 4);
                assertEquals(5, result);
                
                mockControl.verify();
        }

FreeMarker template can look like this

<!-- Freemarker template -->
<plusRequest xmlns="http://javacrumbs.net/calc">
  <a>${a}</a>
  <b>${b}</b>
</plusRequest>

It can be also used in XSLT this way

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
        <xsl:param name="a"/>
        <xsl:param name="b"/>
        <xsl:template match="/">
                        <plusRequest xmlns="http://javacrumbs.net/calc">
                          <a><xsl:value-of select="$a"/></a>
                          <b><xsl:value-of select="$b"/></b>
                        </plusRequest>
                </xsl:template>
</xsl:stylesheet>

Multiple Calls

It's also possible to test multiple calls of a WS in one test. The most straightforward approach is this

@Test
public void testMultiple()
{
        mockControl.expectRequest("request-ignore.xml").atLeastOnce()
                .returnResponse("response2.xml")
                .returnResponse("response3.xml");
        
        assertEquals(5, calc.plus(2, 3));
        assertEquals(8, calc.plus(3, 5));
                        
        mockControl.verify();
}

You can also define multiple expected requests, it's just necessary to define them in correct order

@Test
public void testStrange()
{
        mockControl
            .expectRequest("request1.xml").returnResponse("response1.xml")
                .expectRequest("request-ignore.xml").returnResponse("response2.xml")
                .expectRequest("request1.xml").returnResponse("response1.xml");
        
        assertEquals(3, calc.plus(1, 2));
        assertEquals(5, calc.plus(2, 3));
        assertEquals(3, calc.plus(1, 2));
        
        mockControl.verify();
}

Internals

Unit test support is based on RequestProcessors as the rest of the framework. A RequestProcessor can either validate request, generate response, do nothing or throw an exception. By calling expectRequest, returnResponse, validateSchema and others a new RequestProcessor is created. All request processors used by WsMockControl are implementations of LimitingRequestProcessor. It means that besides their normal functionality they also keep track of number of calls they have processed. Moreover a LimitingRequestProcessor has minimum and maximum number of calls it can process. If less calls were processed an exception is thrown when WsMockControl.verify() was called. All calls over maximum limit are ignored.

By calling atLeastOnce(), times(), ... it is possible to set lower an upper limit on the RequestProcessor inserted last. Default value is one call.

Functional tests

If you find yourself writing tests that result in lot of backend calls, consider using Functional tests instead.