Monday, April 28, 2014

SOAP: Unit Testing with Spring Integration and Spring WS

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.

Add spring ws test dependency:

    <!-- For Testing -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.ws</groupId>
            <artifactId>spring-ws-test</artifactId>
            <version>2.1.4.RELEASE</version>
        </dependency>

Create Client Test using MockWebServiceClient:
package com.fpt.webservices.endpoint;

import static org.springframework.ws.test.server.RequestCreators.withPayload;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/application-context-ws-TEST.xml" })
public class RoleEndPointTest {

    @Autowired
    private ApplicationContext applicationContext;

    private MockWebServiceClient mockClient;

    @Before
    public void createClient() {
        Assert.assertNotNull(applicationContext);
        mockClient = MockWebServiceClient.createClient(applicationContext);
    }

    @Test
    public void testGetRoleResponse() throws IOException {

        String request = "<ns2:RoleRequest xmlns:ns2='urn:springws:springwsservices'>"
                + "        <ns2:roleId>1</ns2:roleId>"
                + "        <ns2:roleName>roleName</ns2:roleName>"
                + "     </ns2:RoleRequest>";

        Source responsePayload = new StringSource(
                "<ns2:RoleResponse xmlns:ns2='urn:springws:springwsservices'>"
                        + "         <ns2:RoleDetails>"
                        + "            <ns2:roleId>1</ns2:roleId>"
                        + "            <ns2:roleName>roleName</ns2:roleName>"
                        + "         </ns2:RoleDetails>"
                        + "      </ns2:RoleResponse>");

        Resource schema = new FileSystemResource("src/main/resources/springws.xsd");

        mockClient.sendRequest(withPayload(new StringSource(request)))
                .andExpect(payload(responsePayload))
                .andExpect(ResponseMatchers.validPayload(schema));
    }
    
    /**
     * For validation
     * 
     * @throws IOException
     */
    @Test
    public void testGetRoleResponseInvalidPayLoad() {

        String request = "<ns2:RoleRequest xmlns:ns2='urn:springws:springwsservices'>"
                + "        <ns2:roleId>roleId</ns2:roleId>"
                + "        <ns2:roleName>roleName</ns2:roleName>"
                + "     </ns2:RoleRequest>";

        Source responsePayload = new StringSource(
                "<ns2:RoleResponse xmlns:ns2='urn:springws:springwsservices'>"
                        + "         <ns2:RoleDetails>"
                        + "            <ns2:roleName>roleName</ns2:roleName>"
                        + "         </ns2:RoleDetails>"
                        + "      </ns2:RoleResponse>");

        mockClient.sendRequest(withPayload(new StringSource(request)))
                .andExpect(payload(responsePayload));
    }
}

Create Client Test using MockWebServiceClient:

package com.fpt.webservices.common;

import java.math.BigInteger;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/application-context-ws-TEST.xml" })
public class RoleClientIntegrationTest {
    @Autowired
    private RoleClient roleClient;
    
    @Autowired
    private WebServiceTemplate webServiceTemplate;

    private MockWebServiceServer mockWebServiceServer;

    @Before
    public void createServer(){
        mockWebServiceServer = MockWebServiceServer.createServer(webServiceTemplate);
    }
    
    @Test
    public void getRoleInfo(){
        String request = "<ns2:RoleRequest xmlns:ns2='urn:springws:springwsservices'>"
                + "        <ns2:roleId>1</ns2:roleId>"
                + "        <ns2:roleName>role name</ns2:roleName>"
                + "     </ns2:RoleRequest>";

        Source responsePayload = new StringSource(
                "<ns2:RoleResponse xmlns:ns2='urn:springws:springwsservices'>"
                        + "         <ns2:RoleDetails>"
                        + "            <ns2:roleId>1</ns2:roleId>"
                        + "            <ns2:roleName>role name</ns2:roleName>"
                        + "         </ns2:RoleDetails>"
                        + "      </ns2:RoleResponse>");
        
        mockWebServiceServer.expect(RequestMatchers.payload(new StringSource(request))).andRespond(ResponseCreators.withPayload(responsePayload));
        
        Role role = roleClient.getRoleInfo();
        Assert.assertEquals(BigInteger.ONE, role.getRoleId());
    }
}


Source Code

SOAP: Spring Web Service

SOAP Web Services provide a platform agnostic integration mechanism that allows disparate systems to exchange data regardless of the platform they are running on.

For example, SOAP web services are commonly used to integrate .NET applications with applications running on the Java platform.
Almost all modern platforms and frameworks (Java, .Net, Ruby, PHP, etc) provide comprehensive libraries and tooling that allow developers to quickly and easily expose and consume SOAP services.

This post will look at Spring Web Services and take you through a step by step tutorial for building, deploying and testing a simple role first SOAP service for retrieving simple role client details.
  1. Create schema file springws.xsd:
    <?xml version="1.0" encoding="UTF-8"?>
    <!-- (c) 2010 DIRECTV, Inc. All rights reserved. -->
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        elementFormDefault="qualified" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
        xmlns:tns="urn:springws:springwsservices" targetNamespace="urn:springws:springwsservices"
        xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
        jaxb:extensionBindingPrefixes="xjc" jaxb:version="2.1">
    
        <xsd:complexType name="Role">
            <xsd:sequence>
                <xsd:element name="roleId" type="xsd:integer" />
                <xsd:element name="roleName" type="xsd:string" />
            </xsd:sequence>
        </xsd:complexType>
    
        <xsd:element name="RoleResponse">
            <xsd:complexType>
                <xsd:sequence>
                    <xsd:element name="RoleDetails" type="tns:Role" />
                </xsd:sequence>
            </xsd:complexType>
        </xsd:element>
    
        <xsd:element name="RoleRequest">
            <xsd:complexType>
                <xsd:sequence>
                    <xsd:element name="roleId" type="xsd:integer" />
                    <xsd:element name="roleName" type="xsd:string" />
                </xsd:sequence>
            </xsd:complexType>
        </xsd:element>
    
    </xsd:schema>
    
  2. Create application context application-context-ws.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:oxm="http://www.springframework.org/schema/oxm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:sws="http://www.springframework.org/schema/web-services"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="    http://www.springframework.org/schema/beans                            
                                http://www.springframework.org/schema/beans/spring-beans-3.0.xsd    
                                http://www.springframework.org/schema/oxm 
                                http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd    
                                http://www.springframework.org/schema/web-services
                                http://www.springframework.org/schema/web-services/web-services-2.0.xsd                    
                                http://www.springframework.org/schema/context
                                http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
        <!-- scans packages to find and register beans and activate annotations 
            within the application context -->
        <context:component-scan base-package="com.fpt.webservices" />
    
        <!-- enable the support for @Endpoint and related Spring-WS annotations -->
        <sws:annotation-driven />
    
        <oxm:jaxb2-marshaller id="marshaller"
            contextPath="com.fpt.webservices.model" />
    
        <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:8081/spring-ws-master/" />
        </bean>
    
        <!-- Our test service bean -->
        <bean id="RoleDetailService"
            class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition"
            lazy-init="true">
            <property name="schemaCollection">
                <list>
                    <bean
                        class="org.springframework.xml.xsd.commons.CommonsXsdSchemaCollection">
                        <property name="inline" value="true" />
                        <property name="xsds">
                            <list>
                                <value>classpath:springws.xsd</value>
                            </list>
                        </property>
                    </bean>
                    <bean
                        class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
                        <property name="validationActions" value="UsernameToken Timestamp" />
                        <property name="validationCallbackHandler">
                            <bean
                                class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler">
                                <property name="users">
                                    <props>
                                        <prop key="Bert">Ernie</prop>
                                    </props>
                                </property>
                            </bean>
                        </property>
                    </bean>
                </list>
            </property>
            <property name="portTypeName" value="RoleDetailService" />
            <property name="serviceName" value="RoleDetailService" />
            <property name="locationUri" value="/endpoints" />
        </bean>
    
        <!--Way 01 For validating your request and response So that you don't send 
            a string instead of an integer -->
        <bean
            class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
            <property name="interceptors">
                <list>
                    <ref local="validatingInterceptor" />
                </list>
            </property>
        </bean>
        <bean id="validatingInterceptor"
            class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
            <property name="schema" value="classpath:springws.xsd" />
            <property name="validateRequest" value="true" />
            <!-- <property name="validateResponse" value="true" /> -->
        </bean>
    </beans>
    
  3. Create EndPoint layer:
    package com.fpt.webservices.endpoint;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.ws.server.endpoint.annotation.Endpoint;
    import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
    import org.springframework.ws.server.endpoint.annotation.RequestPayload;
    import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
    
    import com.fpt.webservices.model.Role;
    import com.fpt.webservices.model.RoleRequest;
    import com.fpt.webservices.model.RoleResponse;
    import com.fpt.webservices.service.RoleService;
    
    @Endpoint
    public class RoleEndPoint {
    
        private static final String TARGET_NAMESPACE = "urn:springws:springwsservices";
        
        @Autowired
        private RoleService roleService;
        
        //http://localhost:8080/spring-ws-master/webservices/RoleDetailService.wsdl
        @PayloadRoot(localPart = "RoleRequest", namespace = TARGET_NAMESPACE)    
        public @ResponsePayload
        RoleResponse getRoleResponse(@RequestPayload RoleRequest roleRequest) {
            RoleResponse roleResponse = new RoleResponse();
            roleResponse = roleService.getRole(new Role(roleRequest.getRoleId(), roleRequest.getRoleName()));
            return roleResponse;
        }
    }
    
  4. Create Service layer:
    package com.fpt.webservices.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import com.fpt.webservices.dao.RoleDao;
    import com.fpt.webservices.model.Role;
    import com.fpt.webservices.model.RoleResponse;
    
    @Service
    public class RoleServiceImpl implements RoleService {
    
        @Autowired
        private RoleDao roleDao;
        
        @Override
        public RoleResponse getRole(Role role) {
            return roleDao.getRole(role);
        }
    }
    
  5. Create Dao layer:
    package com.fpt.webservices.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import com.fpt.webservices.dao.RoleDao;
    import com.fpt.webservices.model.Role;
    import com.fpt.webservices.model.RoleResponse;
    
    @Service
    public class RoleServiceImpl implements RoleService {
    
        @Autowired
        private RoleDao roleDao;
        
        @Override
        public RoleResponse getRole(Role role) {
            return roleDao.getRole(role);
        }
    }
    
    
    
    Source Code