SWS-632 - Create Server-Side testing framework

This commit is contained in:
Arjen Poutsma
2010-11-01 15:14:49 +00:00
parent 2567508ff2
commit 7fd3e49fd4
12 changed files with 228 additions and 348 deletions

View File

@@ -20,20 +20,18 @@ import java.io.IOException;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.xml.transform.TransformerObjectSupport;
/**
* @author Arjen Poutsma
*/
abstract class AbstractRequestCreator<T extends WebServiceMessage> extends TransformerObjectSupport
implements RequestCreator<T> {
abstract class AbstractRequestCreator implements RequestCreator {
public final T createRequest(WebServiceMessageFactory<? extends T> messageFactory) throws IOException {
T request = messageFactory.createWebServiceMessage();
public final WebServiceMessage createRequest(WebServiceMessageFactory messageFactory) throws IOException {
WebServiceMessage request = messageFactory.createWebServiceMessage();
doWithRequest(request);
return request;
}
protected abstract void doWithRequest(T request) throws IOException;
protected abstract void doWithRequest(WebServiceMessage request) throws IOException;
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.mock.server;
import java.io.IOException;
import java.util.Map;
import javax.xml.transform.Source;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.context.DefaultMessageContext;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.mock.support.PayloadDiffMatcher;
import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;
import org.springframework.ws.soap.server.SoapMessageDispatcher;
import org.springframework.ws.transport.WebServiceMessageReceiver;
import org.springframework.xml.transform.ResourceSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import static org.springframework.ws.mock.support.Assert.fail;
/**
* @author Arjen Poutsma
*/
public class MockWebServiceClient {
private static final Log logger = LogFactory.getLog(MockWebServiceClient.class);
private final WebServiceMessageReceiver messageReceiver;
private final WebServiceMessageFactory messageFactory;
private MockWebServiceClient(WebServiceMessageReceiver messageReceiver, WebServiceMessageFactory messageFactory) {
Assert.notNull(messageReceiver, "'messageReceiver' must not be null");
Assert.notNull(messageFactory, "'messageFactory' must not be null");
this.messageReceiver = messageReceiver;
this.messageFactory = messageFactory;
}
// Constructors
public static MockWebServiceClient createClient(WebServiceMessageReceiver messageReceiver,
WebServiceMessageFactory messageFactory) {
return new MockWebServiceClient(messageReceiver, messageFactory);
}
// Factory methods
public static MockWebServiceClient createClient(ApplicationContext applicationContext) {
WebServiceMessageReceiver messageReceiver = getMessageReceiver(applicationContext);
WebServiceMessageFactory messageFactory = getMessageFactory(applicationContext);
return new MockWebServiceClient(messageReceiver, messageFactory);
}
private static WebServiceMessageReceiver getMessageReceiver(ApplicationContext applicationContext) {
WebServiceMessageReceiver messageReceiver = getStrategy(applicationContext, WebServiceMessageReceiver.class);
if (messageReceiver == null) {
if (logger.isDebugEnabled()) {
logger.debug("No WebServiceMessageReceiver found, using default");
}
SoapMessageDispatcher soapMessageDispatcher = new SoapMessageDispatcher();
soapMessageDispatcher.setApplicationContext(applicationContext);
messageReceiver = soapMessageDispatcher;
}
return messageReceiver;
}
private static WebServiceMessageFactory getMessageFactory(ApplicationContext applicationContext) {
WebServiceMessageFactory messageFactory = getStrategy(applicationContext, WebServiceMessageFactory.class);
if (messageFactory == null) {
if (logger.isDebugEnabled()) {
logger.debug("No WebServiceMessageFactory found, using default");
}
SaajSoapMessageFactory saajSoapMessageFactory = new SaajSoapMessageFactory();
saajSoapMessageFactory.afterPropertiesSet();
messageFactory = saajSoapMessageFactory;
}
return messageFactory;
}
private static <T> T getStrategy(ApplicationContext applicationContext, Class<T> strategyInterface) {
Map<String, T> map = applicationContext.getBeansOfType(strategyInterface);
if (map.isEmpty()) {
return null;
}
else if (map.size() == 1) {
Map.Entry<String, T> entry = map.entrySet().iterator().next();
if (logger.isDebugEnabled()) {
logger.debug("Using " + ClassUtils.getShortName(strategyInterface) + " [" + entry.getKey() + "]");
}
return entry.getValue();
}
else {
throw new BeanInitializationException(
"Could not find exactly 1 " + ClassUtils.getShortName(strategyInterface) +
" in application context");
}
}
// Sending
public ResponseActions sendMessage(RequestCreator requestCreator) {
Assert.notNull(requestCreator, "'requestCreator' must not be null");
try {
WebServiceMessage request = requestCreator.createRequest(messageFactory);
MessageContext messageContext = new DefaultMessageContext(request, messageFactory);
messageReceiver.receive(messageContext);
return new MockWebServiceExchange(messageContext);
}
catch (Exception ex) {
fail(ex.getMessage());
return null;
}
}
public ResponseActions sendPayload(Source payload) {
Assert.notNull(payload, "'payload' must not be null");
return sendMessage(new PayloadRequestCreator(payload));
}
public ResponseActions sendPayload(Resource payload) throws IOException {
Assert.notNull(payload, "'payload' must not be null");
return sendMessage(new PayloadRequestCreator(new ResourceSource(payload)));
}
private class MockWebServiceExchange implements ResponseActions {
private final MessageContext messageContext;
private MockWebServiceExchange(MessageContext messageContext) {
Assert.notNull(messageContext, "'messageContext' must not be null");
this.messageContext = messageContext;
}
public ResponseActions andExpect(ResponseMatcher responseMatcher) {
WebServiceMessage response = messageContext.getResponse();
if (response == null) {
fail("No response received");
return null;
}
try {
responseMatcher.match(response);
return this;
}
catch (IOException ex) {
fail(ex.getMessage());
return null;
}
}
public ResponseActions andExpectPayload(Source payload) {
final PayloadDiffMatcher matcher = new PayloadDiffMatcher(payload);
return andExpect(new ResponseMatcher() {
public void match(WebServiceMessage response) throws IOException, AssertionError {
matcher.match(response);
}
});
}
public ResponseActions andExpectPayload(Resource payload) throws IOException {
return andExpectPayload(new ResourceSource(payload));
}
}
}

View File

@@ -21,6 +21,7 @@ import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import org.springframework.ws.WebServiceMessage;
import org.springframework.xml.transform.TransformerHelper;
/**
* Implementation of {@link org.springframework.ws.mock.client.ResponseCreator} that writes a {@link
@@ -29,10 +30,12 @@ import org.springframework.ws.WebServiceMessage;
* @author Arjen Poutsma
* @since 2.0
*/
class PayloadRequestCreator extends AbstractRequestCreator<WebServiceMessage> {
class PayloadRequestCreator extends AbstractRequestCreator {
private final Source payload;
private TransformerHelper transformerHelper = new TransformerHelper();
PayloadRequestCreator(Source payload) {
this.payload = payload;
}
@@ -40,7 +43,7 @@ class PayloadRequestCreator extends AbstractRequestCreator<WebServiceMessage> {
@Override
protected void doWithRequest(WebServiceMessage request) throws IOException {
try {
transform(payload, request.getPayloadResult());
transformerHelper.transform(payload, request.getPayloadResult());
}
catch (TransformerException ex) {
throw new AssertionError("Could not transform request payload to message: " + ex.getMessage());

View File

@@ -24,7 +24,7 @@ import org.springframework.ws.WebServiceMessageFactory;
/**
* @author Arjen Poutsma
*/
public interface RequestCreator<T extends WebServiceMessage> {
public interface RequestCreator {
/**
* Create a request.
@@ -32,7 +32,7 @@ public interface RequestCreator<T extends WebServiceMessage> {
* @param messageFactory the message that can be used to create responses
* @throws java.io.IOException in case of I/O errors
*/
T createRequest(WebServiceMessageFactory<? extends T> messageFactory) throws IOException;
WebServiceMessage createRequest(WebServiceMessageFactory messageFactory) throws IOException;
}

View File

@@ -16,6 +16,11 @@
package org.springframework.ws.mock.server;
import java.io.IOException;
import javax.xml.transform.Source;
import org.springframework.core.io.Resource;
/**
* @author Arjen Poutsma
*/
@@ -28,5 +33,9 @@ public interface ResponseActions {
*/
ResponseActions andExpect(ResponseMatcher responseMatcher);
ResponseActions andExpectPayload(Source payload);
ResponseActions andExpectPayload(Resource payload) throws IOException;
}

View File

@@ -24,7 +24,7 @@ import org.springframework.ws.WebServiceMessage;
* @author Arjen Poutsma
* @since 2.0
*/
public interface ResponseMatcher<T extends WebServiceMessage> {
public interface ResponseMatcher {
/**
* Matches the given response message against the expectations. Implementations typically make use of JUnit-based
@@ -34,6 +34,6 @@ public interface ResponseMatcher<T extends WebServiceMessage> {
* @throws IOException in case of I/O errors
* @throws AssertionError if expectations are not met
*/
void match(T response) throws IOException, AssertionError;
void match(WebServiceMessage response) throws IOException, AssertionError;
}

View File

@@ -1,122 +0,0 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.mock.server;
import java.io.IOException;
import javax.xml.transform.Source;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.context.DefaultMessageContext;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.mock.support.PayloadDiffMatcher;
import org.springframework.ws.transport.WebServiceMessageReceiver;
import org.springframework.xml.transform.ResourceSource;
import static org.springframework.ws.mock.support.Assert.fail;
/**
* @author Arjen Poutsma
*/
public abstract class WebServiceMock {
@SuppressWarnings("unchecked")
public static ResponseActions receiveMessage(RequestCreator requestCreator) {
final WebServiceTestContext testContext = WebServiceTestContextHolder.get();
Assert.state(testContext != null, "No test context found. Did you annotate your test class with " +
"@TestExecutionListeners(WebServiceTestExecutionListener.class) ?");
try {
WebServiceMessageFactory messageFactory = testContext.getMessageFactory();
WebServiceMessage request = requestCreator.createRequest(messageFactory);
MessageContext messageContext = new DefaultMessageContext(request, messageFactory);
WebServiceMessageReceiver messageReceiver = testContext.getMessageReceiver();
messageReceiver.receive(messageContext);
return new ResponseActions() {
public ResponseActions andExpect(ResponseMatcher responseMatcher) {
testContext.addResponseMatcher(responseMatcher);
return this;
}
};
}
catch (Exception ex) {
fail(ex.getMessage());
}
return null;
}
// RequestCreators
public static RequestCreator withPayload(Source payload) {
Assert.notNull(payload, "'payload' must not be null");
return new PayloadRequestCreator(payload);
}
public static RequestCreator withPayload(Resource payload) {
Assert.notNull(payload, "'payload' must not be null");
return new PayloadRequestCreator(createResourceSource(payload));
}
// ResponseMatchers
public static ResponseMatcher payload(Source payload) {
Assert.notNull(payload, "'payload' must not be null");
return createPayloadDiffMatcher(payload);
}
public static ResponseMatcher payload(Resource payload) {
Assert.notNull(payload, "'payload' must not be null");
return createPayloadDiffMatcher(createResourceSource(payload));
}
private static ResponseMatcher createPayloadDiffMatcher(Source payload) {
final PayloadDiffMatcher matcher = new PayloadDiffMatcher(payload);
return new ResponseMatcher() {
public void match(WebServiceMessage response) throws IOException, AssertionError {
matcher.match(response);
}
};
}
/**
* Expects any request.
*
* @return the request matcher
*/
public static ResponseMatcher anything() {
return new ResponseMatcher() {
public void match(WebServiceMessage response) throws IOException, AssertionError {
}
};
}
private static ResourceSource createResourceSource(Resource resource) {
try {
return new ResourceSource(resource);
}
catch (IOException ex) {
throw new IllegalArgumentException(resource + " could not be opened", ex);
}
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.mock.server;
import java.util.ArrayList;
import java.util.List;
import org.springframework.util.Assert;
import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.transport.WebServiceMessageReceiver;
/**
* @author Arjen Poutsma
*/
class WebServiceTestContext {
private final WebServiceMessageReceiver messageReceiver;
private final WebServiceMessageFactory messageFactory;
private final List<ResponseMatcher> responseMatchers = new ArrayList<ResponseMatcher>();
public WebServiceTestContext(WebServiceMessageReceiver messageReceiver, WebServiceMessageFactory messageFactory) {
this.messageReceiver = messageReceiver;
this.messageFactory = messageFactory;
}
WebServiceMessageReceiver getMessageReceiver() {
return messageReceiver;
}
WebServiceMessageFactory getMessageFactory() {
return messageFactory;
}
void addResponseMatcher(ResponseMatcher responseMatcher) {
Assert.notNull(responseMatcher, "'responseMatcher' must not be null");
responseMatchers.add(responseMatcher);
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.mock.server;
import org.springframework.core.NamedThreadLocal;
/**
* @author Arjen Poutsma
* @since 2.0
*/
class WebServiceTestContextHolder {
private static final NamedThreadLocal<WebServiceTestContext> webServiceTestContextHolder =
new NamedThreadLocal<WebServiceTestContext>("Web Service Test Context");
/**
* Associate the given {@link WebServiceTestContext} with the current thread.
*/
public static void set(WebServiceTestContext messageReceiver) {
webServiceTestContextHolder.set(messageReceiver);
}
/**
* Return the {@link WebServiceTestContext} associated with the current thread, if any.
*/
public static WebServiceTestContext get() {
return webServiceTestContextHolder.get();
}
/**
* Clears the holder.
*/
public static void clear() {
set(null);
}
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.mock.server;
import java.util.Map;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.util.ClassUtils;
import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;
import org.springframework.ws.soap.server.SoapMessageDispatcher;
import org.springframework.ws.transport.WebServiceMessageReceiver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author Arjen Poutsma
*/
public class WebServiceTestExecutionListener extends AbstractTestExecutionListener {
private static final Log logger = LogFactory.getLog(WebServiceTestExecutionListener.class);
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
ApplicationContext applicationContext = testContext.getApplicationContext();
WebServiceMessageReceiver messageReceiver = getMessageReceiver(applicationContext);
WebServiceMessageFactory messageFactory = getMessageFactory(applicationContext);
WebServiceTestContext context = new WebServiceTestContext(messageReceiver, messageFactory);
WebServiceTestContextHolder.set(context);
}
private WebServiceMessageReceiver getMessageReceiver(ApplicationContext applicationContext) {
WebServiceMessageReceiver messageReceiver = getStrategy(applicationContext, WebServiceMessageReceiver.class);
if (messageReceiver == null) {
if (logger.isDebugEnabled()) {
logger.debug("No WebServiceMessageReceiver found, using default");
}
SoapMessageDispatcher soapMessageDispatcher = new SoapMessageDispatcher();
soapMessageDispatcher.setApplicationContext(applicationContext);
messageReceiver = soapMessageDispatcher;
}
return messageReceiver;
}
private WebServiceMessageFactory getMessageFactory(ApplicationContext applicationContext) throws Exception {
WebServiceMessageFactory messageFactory = getStrategy(applicationContext, WebServiceMessageFactory.class);
if (messageFactory == null) {
if (logger.isDebugEnabled()) {
logger.debug("No WebServiceMessageFactory found, using default");
}
SaajSoapMessageFactory saajSoapMessageFactory = new SaajSoapMessageFactory();
saajSoapMessageFactory.afterPropertiesSet();
messageFactory = saajSoapMessageFactory;
}
return messageFactory;
}
private <T> T getStrategy(ApplicationContext applicationContext, Class<T> strategyInterface) {
Map<String, T> map = applicationContext.getBeansOfType(strategyInterface);
if (map.isEmpty()) {
return null;
}
else if (map.size() == 1) {
Map.Entry<String, T> entry = map.entrySet().iterator().next();
if (logger.isDebugEnabled()) {
logger.debug("Using " + ClassUtils.getShortName(strategyInterface) + " [" + entry.getKey() + "]");
}
return entry.getValue();
}
else {
throw new BeanInitializationException("Could not find exactly 1 Message Dispatcher in application context");
}
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
WebServiceTestContextHolder.clear();
}
}

View File

@@ -18,38 +18,43 @@ package org.springframework.ws.mock.server.integration;
import javax.xml.transform.Source;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.ws.mock.server.WebServiceTestExecutionListener;
import org.springframework.ws.mock.server.MockWebServiceClient;
import org.springframework.xml.transform.StringSource;
import org.junit.Ignore;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.springframework.ws.mock.server.WebServiceMock.*;
/**
* @author Arjen Poutsma
*/
@Ignore
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("integration-test.xml")
@TestExecutionListeners(WebServiceTestExecutionListener.class)
public class ServerIntegrationTest {
@Autowired
private ApplicationContext applicationContext;
private MockWebServiceClient mockClient;
@Before
public void createClient() {
mockClient = MockWebServiceClient.createClient(applicationContext);
}
@Test
public void basic() throws Exception {
Source requestPayload = new StringSource("<customerCountRequest xmlns='http://springframework.org/spring-ws'>" +
"<customerName>John Doe</customerName>" + "</customerCountRequest>");
Source responsePayload = new StringSource(
"<customerCountResponse xmlns='http://springframework.org/spring-ws'>" +
"<customerCount>10</customerCount>" + "</customerCountResponse>");
"<customerCount>42</customerCount>" + "</customerCountResponse>");
// expect(payload(responsePayload)).andExpect(anything()).whenReceivingRequest(withPayload(requestPayload));
receiveMessage(withPayload(requestPayload)).andExpect(payload(responsePayload));
mockClient.sendPayload(requestPayload).andExpectPayload(responsePayload);
}

View File

@@ -3,7 +3,7 @@
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.xsd">
<bean class="org.springframework.ws.server.endpoint.mapping.XmlRootElementEndpointMapping"/>
<bean class="org.springframework.ws.server.endpoint.mapping.jaxb.XmlRootElementEndpointMapping"/>
<bean class="org.springframework.ws.mock.server.integration.CustomerEndpoint"/>