Announcement Announcement Module
Collapse
No announcement yet.
Unit Testing With Spring Web Services MockWebServiceServer Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Unit Testing With Spring Web Services MockWebServiceServer

    This thread is related to http://forum.springsource.org/showth...y-testshttp:// and http://forum.springsource.org/showth...onsumerhttp://.

    I have been able to attach my MarshallingWebServiceOutboundGateway to a MockWebServiceServer using reflection. The test works wonderfully all paths through my messaging config.

    I want to know if there is a better way to do this, or plans to make this easier in the future.

    Here's what I've done for now
    Code:
    package x.y.z;
    
    import org.apache.commons.lang.StringUtils;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.integration.ws.MarshallingWebServiceOutboundGateway;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.ws.client.core.WebServiceTemplate;
    import org.springframework.ws.test.client.MockWebServiceServer;
    
    import java.lang.reflect.Method;
    
    import static org.junit.Assert.assertEquals;
    import static org.junit.Assert.assertNotNull;
    import static org.junit.Assert.assertTrue;
    import static org.junit.Assert.fail;
    import static org.springframework.ws.test.client.RequestMatchers.anything;
    import static org.springframework.ws.test.client.ResponseCreators.withSoapEnvelope;
    
    @RunWith( SpringJUnit4ClassRunner.class )
    @ContextConfiguration( { ... the usual suspects ...  } )
    public class SoapHandlingTest {
    
      private final static Logger LOG = LoggerFactory.getLogger( SoapHandlingTest.class );
    
      @Autowired
      private Request request;
      @Autowired
      private Service service;
      @Autowired
      @Qualifier( "org.springframework.integration.ws.MarshallingWebServiceOutboundGateway#0" )
      private MarshallingWebServiceOutboundGateway ourOnlyWsOutboundGateway;
    
      private MockWebServiceServer mockServer;
    
      @Before
      public void setUp() {
        try {
          Method getWebServiceTemplate = ourOnlyWsOutboundGateway.getClass().getSuperclass().getDeclaredMethod( "getWebServiceTemplate" );
          getWebServiceTemplate.setAccessible( true );
          WebServiceTemplate webServiceTemplate = (WebServiceTemplate)getWebServiceTemplate.invoke( ourOnlyWsOutboundGateway );
          mockServer = MockWebServiceServer.createServer( webServiceTemplate );
        }
        catch ( Exception e ) {
          LOG.debug( getClass().getName(), e );
        }
      }
    
      @After
      public void after() {
        mockServer.verify();
      }
    
      /*
     Should load application context.
      */
      @Test
      public void testApplicationContext() {
      }
    
    
      /*
     Should handle response with SoapFault when request is malformed.
      */
      @Test
      public void testMalformedRequestResponse() throws Exception {
        mockServer.expect( anything() )
            .andRespond( withSoapEnvelope( new ClassPathResource( "ResponseMalformedRequest.xml", SoapFaultHandler.class ) ) );
    
        Response result = service.query(request);
    
        assertNotNull( result );
        assertEquals( StringUtils.EMPTY, result.getResponse().getErrorCode() );
        assertEquals( result.getErrorDetail(), result.getResponse().getErrorDescription() );
        assertEquals( "A parsing error probably occurred because the request message was not constructed properly.", result.getErrorDetail() );
        assertEquals( WebServiceCallResponseType.ERROR, result.getResponseType() );
      }
    }
    Here's my configuration. It is safe to assume all of the supporting beans are in place because my tests work.

    Code:
    <?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:int-ws="http://www.springframework.org/schema/integration/ws"
           xmlns:oxm="http://www.springframework.org/schema/oxm"
           xmlns:util="http://www.springframework.org/schema/util"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:c="http://www.springframework.org/schema/c"
           xmlns:int="http://www.springframework.org/schema/integration"
           xsi:schemaLocation="http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm.xsd
    		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
    		http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
    		http://www.springframework.org/schema/integration/ws http://www.springframework.org/schema/integration/ws/spring-integration-ws.xsd">
    
      <import resource="classpath:core-constants.xml"/>
    
      <int:channel id="RequestChannel"/>
      <int:channel id="ServiceChannel"/>
      <int:channel id="IntoWS"/>
      <int:channel id="IntoChain"/>
      <int:channel id="OuttaChain"/>
    
      <int:channel id="ErrorChannel">
        <int:interceptors>
          <int:wire-tap channel="HeadersLogger"/>
        </int:interceptors>
      </int:channel>
      <int:logging-channel-adapter id="HeadersLogger" level="ERROR" expression="headers"/>
    
    
      <int:gateway id="Service" service-interface="x.y.z.SomeServiceInterface" default-request-channel="RequestChannel"/>
    
    
      <int:service-activator input-channel="RequestChannel" ref="SegmentGateway"/>
      <int:gateway id="SegmentGateway" default-request-channel="ServiceChannel" error-channel="ErrorChannel" default-reply-channel="OuttaChain" />
    
    
    <int:chain id="HappyPathChainBeforeWebService" input-channel="ServiceChannel" output-channel="IntoWS">
        <int:header-enricher> <int:header ... one of many ... /> </int:header-enricher>
        <int:transformer ref="Transformer"/>
        <int-ws:header-enricher> <int-ws:soap-action value="#{soapActions['y']}"/> </int-ws:header-enricher>
    </int:chain>
    
    <int-ws:outbound-gateway id="OutboundGateway"
                                 interceptors="Interceptors"
                                 destination-provider="DestinationProvider"
                                 unmarshaller="Marshaller"
                                 marshaller="Marshaller"
                                 fault-message-resolver="SoapFaultMessageResolver"
                                 message-sender="HttpSender"
                                 message-factory="SaajMessageFactory"
                                 request-channel="IntoWS"
                                 reply-channel="IntoChain"/>
    
    
    <int:chain id="HappyPathChainAfterWebService" input-channel="IntoChain" output-channel="OuttaChain">
        <int:header-enricher> <int:header ... one of many ... /> </int:header-enricher>
        <int:service-activator method="doSomething" ref="Z"/>
        <int:transformer ref="T"/>
      </int:chain>
    
      <int:chain id="ErrorPathChain" input-channel="ErrorChannel" output-channel="OuttaChain">
        <int:header-enricher> <int:header ... one of many ... /> </int:header-enricher>
        <int:transformer ref="ErrorMessageTransformer"/>
        <int:service-activator method="doSomething" ref="Z"/>
      </int:chain>
    
     
      <oxm:jaxb2-marshaller id="Marshaller" contextPath="#{marshallerContextPaths['x']}"/>
    
    </beans>
    I'm using these Spring versions:
    revision.springframework = 3.0.6
    revision.spring-int = 2.1.3
    revision.spring-ws = 2.1.0
    Last edited by kxen; Apr 18th, 2013, 04:24 PM. Reason: Add Spring versions.

  • #2
    We have a few tools to make this a "little" easier...

    The first is in spring-integration-test - a utility called TestUtils.

    You can get a reference to a field via a dotted path.

    So, if you inject the endpoint (as you had done previously), you could use...

    Code:
    	@Autowired @Qualifier("foo.bar")
    	private EventDrivenConsumer gateway;
    
    	@Test
    	public void test() {
    		WebServiceTemplate template = TestUtils.getPropertyValue(gateway,
    				"handler.webServiceTemplate",
    				WebServiceTemplate.class);
    		MockWebServiceServer server = MockWebServiceServer.createServer(template);
    ...
    Another useful utility is in Spring Framework (DirectFieldAccessor); we use this in a number of tests, together with Mockito to "spy" on some field...

    Code:
    		LoggingHandler loggingHandler = new LoggingHandler("INFO");
    		DirectFieldAccessor accessor = new DirectFieldAccessor(loggingHandler);
    		Log log = (Log) accessor.getPropertyValue("messageLogger");
    		log = spy(log);
    		accessor.setPropertyValue("messageLogger", log);
    ...
    		when(log.isInfoEnabled()).thenReturn(true);
    It can also be used to completely replace a field with something else - if you explore the spring-integration tests, you'll see lots of use of these tools.

    In both cases, these tools access the fields directly (rather than via the getter as you did).

    Hope that helps.

    Comment

    Working...
    X