Spring WS 2.0 introduced a new integration testing module, spring-ws-test, which provides a fluent API for testing both the client- and server-side of Web service. In my previous article on Spring WS 2.0 and SOAP-based Web services , I explored some of the new features introduced in the 2.0 release. In this installment, I’ll dive into spring-ws-test. In the previous article, we exposed a SOAP Web service endpoint (using @Endpoint
annotation) on the server, but we did not write the client side. The client side can be written using Spring WS, but it is not mandatory as you can generate client-side code from WSDL using any other framework such as Axis. The advantages of using Spring WS to write client code are:
- Spring WS provides a lightweight alternative that does not need a WSDL contract to work. This means that you are free from any code generation (i.e. generating client code from WSDL).
- Spring WS requires no dependency on any other Web service framework.
Spring WS 2.0 WebServiceTemplate for Common Tasks
Spring WS provides a class called WebServiceTemplate that developers can use to perform common operations. It follows the same design principle as other Spring templates like JdbcTemplate do. The template provides convenience methods to perform the most common operations and provides a callback interface mechanism for more sophisticated use cases. For example, it provides the callback interface WebServiceMessageCallback, which lets developers perform any operation on a Web service message.
WebServiceTemplate provides methods for sending and receiving XML messages (sendAndReceive()
) or request and response objects (marshalSendAndReceive()
). To work with request and response objects instead of XML messages, you need to configure a marshaller for converting an object to an XML message and an unmarshaller to convert an XML response message back to a response object.
In this article, I use JAXB2 for marshalling and unmarhalling. Please make sure that you have JAXB2 JARs in your classpath. Let’s begin by writing the client for the Web service we wrote in the previous article using WebServiceTemplate. The code snippet written below gets the WebServiceTemplate bean from the client application context (refer profile-service-client.xml). The WebServiceTemplate marshalSendAndReceive method takes the UserProfileCreateRequest object as a request and returns the UserProfileCreateResponse object as a response. The marshaller is configured in profile-service-client.xml using the oxm:jaxb2-marshaller
tag. The oxm:jaxb2-marshaller
tag resides in the http://www.springframework.org/schema/oxm namespace. You can test the client by starting the server using mvn jetty:run
and then running the Client class.
import java.math.BigInteger;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.ws.client.core.WebServiceTemplate;
import com.shekhar.usermanagement.profile.UserProfile;
import com.shekhar.usermanagement.profile.UserProfileCreateRequest;
import com.shekhar.usermanagement.profile.UserProfileCreateResponse;
public class Client {
WebServiceTemplate webServiceTemplate;
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"com/shekhar/usermanagement/profileService/ws/client/profile-service-client.xml");
WebServiceTemplate template = applicationContext
.getBean(WebServiceTemplate.class);
Client client = new Client();
client.webServiceTemplate = template;
client.invokeProfileServiceAndGetASuccessResponse();
}
public String invokeProfileServiceAndGetASuccessResponse() {
UserProfileCreateRequest request = new UserProfileCreateRequest();
UserProfile userProfile = new UserProfile();
userProfile.setAge(BigInteger.valueOf(27));
userProfile.setFirstName("Shekhar");
userProfile.setLastName("Gulati");
request.setUserProfile(userProfile);
UserProfileCreateResponse response = (UserProfileCreateResponse) webServiceTemplate
.marshalSendAndReceive(request);
System.out.println(response.getMessage());
return response.getMessage();
}
}
The client context XML file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
jaxb2-marshaller id="marshaller"
contextPath="com.shekhar.usermanagement.profile" />
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<property name="marshaller" ref="marshaller" />
<property name="unmarshaller" ref="marshaller" />
<property name="defaultUri" value="http://localhost:8080/profileService/" />
</bean>
</beans>
The client and server code can be very easily unit tested by mocking out the external dependencies using any of the mocking frameworks like EasyMock or Mockito. The problem with unit testing, however, is that it does not test the exact content of the XML message.
Integration Testing Support in Spring WS 2.0
Spring WS 2.0 provides an integration test framework, which will help you test the content of the message. It provides support for testing both client-side (written using WebServiceTemplate) and server-side (having @Endpoint
annotation) code. The best part of using this framework is that you do not have to deploy on the actual server to do the integration testing. It provides a mock server (MockWebServiceServer) for the client side and a mock client (MockWebServiceClient) for the server side. Let’s write the client and server-side integration test for our service.
Client-side Integration Testing
The main class for writing integration tests on the client side is MockWebServiceServer. This server accepts a request message, verifies it against the expected request messages, and then returns the response message.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("profile-service-client.xml")
public class ProfileServiceClientIntegrationTest {
private String request = "<ns2:UserProfileCreateRequest xmlns_ns2='http://shekhar.com/usermanagement/schemas'> Shekhar Gulati 27 ";
private String response = "<ns2:UserProfileCreateResponse xmlns_ns2='http://shekhar.com/usermanagement/schemas'> user created successfully ";
@Autowired
private WebServiceTemplate webServiceTemplate;
private MockWebServiceServer server;
@Autowired
private ProfileServiceClient client;
@Before
public void setup() {
server = MockWebServiceServer.createServer(webServiceTemplate);//1
}
@Test
public void testInvokeProfileServiceAndGetASuccessResponse() {
server.expect(RequestMatchers.payload(new StringSource(request))).andRespond(ResponseCreators.withPayload(new StringSource(response)));//2
Assert.assertEquals("user created successfully",client.invokeProfileServiceAndGetASuccessResponse());//3
server.verify();//4
}
}
The flow is :
- Create a MockWebServiceServer using a WebServiceTemplate. You can also create MockWebServiceServer using WebServiceGatewaySupport or using ApplicationContext.
- Set up request expectations using RequestMatcher and return response using ResponseCreator.
- Make a client call.
- Call the verify method to make sure all the expectations are met.
There are more tests that you can perform using MockWebServiceServer, such as testing for SoapFaultException, performing validation, etc. The mock server uses RequestMatcher to match the expected request and uses ResponseCreator to create the response to be sent back to client. You can also write your own matcher and creator by implementing the RequestMatcher and ResponseCreator interfaces. All the default implementations exist in the RequestMatchers and ResponseCreators utility classes.
@Test
public void shouldInvokeProfileServiceAndGetASuccessResponse()
throws Exception {
Resource schema = new FileSystemResource(
"src/main/webapp/WEB-INF/userManagement.xsd");
server.expect(RequestMatchers.payload(new StringSource(request)))
.andExpect(RequestMatchers.validPayload(schema))
.andRespond(
ResponseCreators
.withPayload(new StringSource(response)));
Assert.assertEquals("user created successfully",
client.invokeProfileServiceAndGetASuccessResponse());
server.verify();
}
@Test(expected = RuntimeException.class)
public void shouldThrowRuntimeException() {
server.expect(RequestMatchers.payload(new StringSource(request)))
.andRespond(
ResponseCreators.withException(new RuntimeException()));
client.invokeProfileServiceAndGetASuccessResponse();
server.verify();
}
@Test(expected = SoapFaultClientException.class)
public void shouldThrowSoapFault() {
server.expect(RequestMatchers.payload(new StringSource(request)))
.andRespond(
ResponseCreators.withServerOrReceiverFault(
"Soap Fault Exception", Locale.US));
client.invokeProfileServiceAndGetASuccessResponse();
server.verify();
}
Server-side Integration Testing
The main class for writing integration tests on the server side is MockWebServiceClient. The client creates a request message, sends the request to the service endpoint, which processes the request, and creates a response message. The client then receives the response message and verifies it against the expected response. It makes it very easy to test your SOAP service without deploying on the server and without the need for a client.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("spring-ws-servlet-test.xml")
public class UserProfileEndpointIntegrationTest {
@Autowired
private ApplicationContext applicationContext;
private MockWebServiceClient mockClient;
@Before
public void createClient() {
mockClient = MockWebServiceClient.createClient(applicationContext);
}
@Test
public void testCreate() {
String request = "<ns2:UserProfileCreateRequest xmlns_ns2='http://shekhar.com/usermanagement/schemas'> Shekhar Gulati 27 ";
String response = "<ns2:UserProfileCreateResponse xmlns_ns2='http://shekhar.com/usermanagement/schemas'> user created successfully ";
Assert.assertNotNull(applicationContext);
mockClient
.sendRequest(
RequestCreators.withPayload(new StringSource(request)))
.andExpect(ResponseMatchers.payload(new StringSource(response)));
}
}
The mock client uses RequestCreator to create the request to be sent to the server and uses ResponseMatcher to match the response to be created by server. You can also write your own creator and matchers by implementing RequestCreator and ResponseMatcher interfaces. All the default implementations exist in RequestCreators and ResponseMatchers utility classes.
Conclusion
The introduction of an integration testing framework in Spring WS 2.0 is a very useful addition. It makes writing integration tests for both client- and server-side code very easy.
Download the Code
Download the profileService code from my github repository.
About the Author
Shekhar Gulati — Contributing Editor, Java — is a Java consultant with over 5 years experience. He currently works with Xebia India, an Agile Software Development company. The opinions in this article and on his blog are his own and do not necessarily represent the opinions of his employer. His own blog is at and you can follow him on twitter here.