Saturday, July 9, 2011

Testing CXF based SOAP webservice outside a web container

Introduction

This post is an example of writing a test for CXF based SOAP service outside a web container.

I see that the first choice for writing a SOAP based test is usually SOAPUI. SOAPUI definitely has a place if we want to do some end to end functional testing of webservices.

However to come up with full suite of tests, one needs to resort to groovy for all the setup and tear down activity. This puts one in an uncomfortable position.

I think with the approach outlined here one could do pure unit level testing of your webservice code or have integration tests with underlying Database. And you get to do this in the comfort of JUNIT and Java and your favourite IDE. In other words no learning curve whatsoever, except ofcourse writing effective tests!!

In order to stay to the point, I have used a very simple example to show how we can write a unit test for CXF based SOAP service using Spring and JUnit4


Sample WebService


Is ingeniously called SampleWebService with a single servcie operation


public String sayHelloTo(yourName)



Sample Web Service Unit Test


This is where we have majority of the configuration and plumbing code.


Spring configuration

The following spring configuration defines a local(VM) address for the service



<beans xmlns="http://www.springframework.org/schema/beans" xsi="http://www.w3.org/2001/XMLSchema-instance" jaxws="http://cxf.apache.org/jaxws" schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml">
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml">


<bean id="localTransportFactory" class="org.apache.cxf.transport.local.LocalTransportFactory">
</bean>
<bean id="sampleWebService" class="com.apache.cxf.sample.SampleWebService">
<jaxws:endpoint tns="http://sample.cxf.apache.com/" id="localEndpoint" implementor="#sampleWebService" address="local://SampleWebServicePort">
<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature">
</bean></jaxws:features>
</jaxws:endpoint>
</bean>
</import></import></beans>



Sample WebService Unit test class

The JUnit test class uses SpringJUnit4ClassRunner to inject the beans defined in the above spring config file.

The code to get an handle to the webservice client is obtained using the low level standard XML and Webservice APIs as seen in this code snippet


private ISampleWebService createSampleWebServiceClient() {
QName serviceName = new QName("http://sample.cxf.apache.com/",
"SampleWebService");
QName portName = new QName("http://sample.cxf.apache.com/",
"SampleWebServicePort");

Service service = Service.create(serviceName);
service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING,
"local://SampleWebServicePort");
ISampleWebService client = service.getPort(
portName, com.apache.cxf.sample.ISampleWebService.class);
return client;
}


The above code eliminates the need to do any java to wsdl and back to client side stub code generation.

Below is the standard plumbing code required to initialize the local CXF transport.


private void setupLocalTransport() {
LocalTransportFactory localTransportFactory = (LocalTransportFactory) applicationContext
.getBean("localTransportFactory");
ConduitInitiatorManager cim = bus
.getExtension(ConduitInitiatorManager.class);
cim.registerConduitInitiator("http://cxf.apache.org/transports/local",
localTransportFactory);
cim.registerConduitInitiator(
"http://schemas.xmlsoap.org/wsdl/soap/http",
localTransportFactory);
cim.registerConduitInitiator("http://schemas.xmlsoap.org/soap/http",
localTransportFactory);
cim.registerConduitInitiator("http://cxf.apache.org/bindings/xformat",
localTransportFactory);
}



This can be easily refactored and moved to a base class to keep the tests cleaner.


Finally one needs following maven dependencies to have a running maven project.

<dependencies>
<dependency>
<groupid>commons-logging</groupid>
<artifactid>commons-logging</artifactid>
<version>1.1.1</version>
</dependency>
<dependency>
<groupid>commons-util</groupid>
<artifactid>commons-util</artifactid>
<version>final</version>
</dependency>
<dependency>
<groupid>commons-lang</groupid>
<artifactid>commons-lang</artifactid>
<version>20030203.000129</version>
</dependency>
<dependency>
<groupid>junit</groupid>
<artifactid>junit</artifactid>
<version>4.8.2</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-test</artifactid>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupid>org.apache.cxf</groupid>
<artifactid>cxf-rt-transports-local</artifactid>
<version>2.4.1</version>
</dependency>

<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-core</artifactid>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-context</artifactid>
<version>3.0.5.RELEASE</version>
</dependency>

<dependency>
<groupid>org.apache.cxf</groupid>
<artifactid>cxf-rt-bindings-soap</artifactid>
<version>2.4.1</version>
</dependency>
<dependency>
<groupid>org.apache.cxf</groupid>
<artifactid>cxf-rt-frontend-jaxws</artifactid>
<version>2.4.1</version>
</dependency>
</dependencies>


Hope this helps folks out there.

1 comment:

  1. Nice post, thanks.
    I came here searching for a (slightly?) different problem. I have a CXF client for a remote service, I'd like to test my client code with a mock service class, not the actual service.

    Can you help me figure out how to do it? CXF website is not helping much.

    ReplyDelete