Thursday, October 15, 2009

Spring, Web Services and Functional Testing

Functional tests are particularly interesting for web services as they allow to test a fully integrated web service stack from the outside, thus giving the same point of view of the clients. In a sense, what really matters in a web service is its interface rather than its implementation, and functional testing allows to focus on that.

In this post I'd like to present an automated way to implement functional tests for your web services using Spring. The approach relies mainly on Spring TestContext Framework, an embedded Jetty instance and Spring Web Services (you saw it coming, didn't you?).
The outline is as follows: functional tests are implemented using Spring TestContext and JUnit 4 (or any of the other supported testing frameworks). In the ApplicationContext that Spring TestContext creates for the test, we include an embedded Jetty instance that loads and runs the target web service application. The actual test consists simply of invoking that web service through a WebServiceTemplate (Spring-WS) and validating the response with XMLUnit. Finally, we run our tests using Maven (mvn test, simply).
First, let me tell you that if you're not familiar with the Spring TestContext Framework, you don't know what you're missing. I'll quote the reference manual for a quick definition:
The Spring TestContext Framework (located in the org.springframework.test.context
package) provides generic, annotation-driven unit and integration testing support that is agnostic of the testing framework in use (...). The TestContext framework also places a great deal of importance on convention over configuration with reasonable defaults that can be overridden via annotation-based configuration.
Without delving into all the features of Spring TestContext here, what you need to know is that the framework creates and caches an ApplicationContext instance -basically, a full-fledged Spring container- for every test class and allows to inject dependencies into your test classes. There are a number of other useful features that we will not use here, but already the features I just mentioned are extremely valuable as you will see next.

An Example

Let's illustrate the concept on a simple web service. I'm using Spring-WS to implement the web service to test but the implementation framework is really irrelevant - you can do the same thing with your favorite one.
The application that we will be testing is a weather service that receives a request containing the coordinates of a specific location and returns the temperature there.
For example, if you're anxious to know what the temperature is in Easter Island, you can send a SOAP request containing a payload similar to the following:
<WeatherRequest xmlns="http://www.cafebabe.me/weather-service">
    <Location>
        <Longitude>109:25:30W</Longitude>
        <Latitude>27:9:0S</Latitude>
    </Location>
</WeatherRequest>
The response will look something like this:
<WeatherResponse xmlns="http://www.cafebabe.me/weather-service">
    <Temperature>30.0</Temperature>
</WeatherResponse>
Let's set up the testing environment now. First we need an instance of embedded Jetty. We will put this in its own separate bean definition file to make it reusable from different tests:
<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:property-placeholder
        location="classpath:me/cafebabe/weather/ws/jetty.properties" />

    <bean id="server" class="org.mortbay.jetty.Server" init-method="start"
        destroy-method="stop">

        <property name="connectors">
            <list>
                <bean id="Connector" class="org.mortbay.jetty.nio.SelectChannelConnector">
                    <property name="port" value="${jetty.port}" />
                </bean>
            </list>
        </property>

        <property name="handler">
            <bean id="handlers" class="org.mortbay.jetty.handler.HandlerCollection">
                <property name="handlers">
                    <list>
                        <bean id="contexts" class="org.mortbay.jetty.handler.ContextHandlerCollection">
                            <property name="handlers">
                                <list>
                                    <bean class="org.mortbay.jetty.webapp.WebAppContext">
                                        <property name="contextPath" value="/${jetty.contextPath}" />
                                        <property name="war" value="src/main/webapp" />
                                    </bean>
                                </list>
                            </property>
                        </bean>
                    </list>
                </property>
            </bean>
        </property>
    </bean>
</beans>
Those bean definitions are all what we need to configure and bootstrap a minimal Jetty instance (notice the init-method and destroy-method on the server bean). Jetty will deploy the application directly from the Maven project directories (see the war property), which is particularly nice since the project structure is preserved and no additional configuration is needed.
We will also externalize Jetty properties in a separate file to reference them from our tests:
jetty.port=8282
jetty.contextPath=
Now we can write a simple functional test that invokes the weather service and validates the return message. We need to inject a Resource pointing to the file that contains the test request and a WebServiceTemplate to invoke the service. Aside from that there is a simple initialization method to set up the namespaces for XMLUnit:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class WeatherServiceIntegrationTest {

    @Autowired
    private WebServiceTemplate webServiceTemplate;

    @Autowired
    private Resource request;

    @BeforeClass
    public static void setUpNamespaces() throws Exception {
        Map<String, String> namespaces = new HashMap<String, String>();
        namespaces.put("w", "http://www.cafebabe.me/weather-service");
        NamespaceContext ctx = new SimpleNamespaceContext(namespaces);
        XMLUnit.setXpathNamespaceContext(ctx);
    }

    @Test
    public void testInvoke() throws Exception {
        Source requestSource = new ResourceSource(request);
        StringResult result = new StringResult();
        webServiceTemplate.sendSourceAndReceiveToResult(requestSource, result);
        XMLAssert.assertXpathExists("/w:WeatherResponse/w:Temperature/text()", result
                .toString());
    }
}
Notice how in testInvoke we use the convenient ResourceSource and StringResult from Spring-WS to read the request from a resource and to write the response to a String respectively.

The final missing piece is the bean definition file for WeatherServiceIntegrationTest. Since the class is simply annotated with @ContextConfiguration, Spring TestContext will automatically look for a bean definition file named WeatherServiceIntegrationTest-context.xml under the same package. The file simply imports the Jetty bean definition file and defines the WebServiceTemplate and the request Resource beans needed for the test:
<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-2.5.xsd">

    <import resource="jetty.xml"/>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <property name="defaultUri" value="http://localhost:${jetty.port}/${jetty.contextPath}" />
    </bean>
 
    <bean id="request" class="org.springframework.core.io.ClassPathResource">
        <constructor-arg value="me/cafebabe/weather/ws/request.xml"/>
    </bean>
 
</beans>
To sum up, here's what happens when we run "mvn test":
  1. An application context is created for WeatherServiceIntegrationTest from the WeatherServiceIntegrationTest-context.xml file.
  2. An embedded Jetty server starts and loads the web service application directly from the Maven project.
  3. The testInvoke method runs and invokes the web service using a WebServiceTemplate.
  4. Jetty shuts down gracefully when the test completes.
    The main advantage of this approach is that it automates functional tests while leaving the testing code clean and uncluttered. Meanwhile, the project structure remains intact. As I'm writing, I'm thinking that there is no reason that this should be limited to SOAP web services; a fairly identical setting should be possible for Rest services with the newly introduced RestTemplate (Spring 3.0). Maybe this deserves a follow-up..

    You can download the sample code here.