Use XMLUnit 2 for RequestMatcher and ResponseMatcher.

Implement newer versions of PayloadDiffMatcher and SoapEnvelopematcher based on XMLUnit 2. Deprecate the prior versions of these supporting types so they can be removed in a future release. XMLUnit 2 eases the testing of MockWebServiceServer.

Resolves #1193.
This commit is contained in:
Alberic Martel
2022-12-08 17:18:39 +01:00
committed by Greg L. Turnquist
parent 43b6ecf13f
commit c39d3d1cf0
14 changed files with 460 additions and 19 deletions

View File

@@ -122,7 +122,7 @@
<xmlsec.version>2.3.1</xmlsec.version>
<xml-schema-core.version>2.2.2</xml-schema-core.version>
<xmlunit1.version>1.6</xmlunit1.version>
<xmlunit.version>2.7.0</xmlunit.version>
<xmlunit.version>2.9.0</xmlunit.version>
<xws-security.version>3.0</xws-security.version>
<xom.version>1.2.5</xom.version>
<spring-asciidoctor-backends.version>0.0.3</spring-asciidoctor-backends.version>

View File

@@ -40,6 +40,12 @@
<version>${xmlunit1.version}</version>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<version>${xmlunit.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
@@ -76,4 +82,4 @@
</profile>
</profiles>
</project>
</project>

View File

@@ -35,7 +35,7 @@ import org.springframework.ws.transport.WebServiceConnection;
*/
class MockSenderConnection implements WebServiceConnection, ResponseActions {
private final List<RequestMatcher> requestMatchers = new LinkedList<RequestMatcher>();
private final List<RequestMatcher> requestMatchers = new LinkedList<>();
private URI uri;

View File

@@ -25,11 +25,10 @@ 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.test.support.matcher.PayloadDiffMatcher;
import org.springframework.ws.test.support.matcher.SchemaValidatingMatcher;
import org.springframework.ws.test.support.matcher.SoapEnvelopeDiffMatcher;
import org.springframework.ws.test.support.matcher.SoapHeaderMatcher;
import org.springframework.ws.test.support.matcher.xmlunit2.PayloadDiffMatcher;
import org.springframework.ws.test.support.matcher.xmlunit2.SoapEnvelopeDiffMatcher;
import org.springframework.xml.transform.ResourceSource;
/**
@@ -49,9 +48,7 @@ public abstract class RequestMatchers {
* @return the request matcher
*/
public static RequestMatcher anything() {
return new RequestMatcher() {
public void match(URI uri, WebServiceMessage request) throws IOException, AssertionError {}
};
return (uri, request) -> {};
}
// Payload

View File

@@ -16,7 +16,7 @@
package org.springframework.ws.test.server;
import static org.springframework.ws.test.support.AssertionErrors.*;
import static org.springframework.ws.test.support.AssertionErrors.fail;
import java.io.IOException;
import java.util.Locale;
@@ -30,10 +30,10 @@ import org.springframework.util.Assert;
import org.springframework.ws.FaultAwareWebServiceMessage;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.soap.SoapVersion;
import org.springframework.ws.test.support.matcher.PayloadDiffMatcher;
import org.springframework.ws.test.support.matcher.SchemaValidatingMatcher;
import org.springframework.ws.test.support.matcher.SoapEnvelopeDiffMatcher;
import org.springframework.ws.test.support.matcher.SoapHeaderMatcher;
import org.springframework.ws.test.support.matcher.xmlunit2.PayloadDiffMatcher;
import org.springframework.xml.transform.ResourceSource;
/**

View File

@@ -16,7 +16,7 @@
package org.springframework.ws.test.support.matcher;
import static org.springframework.ws.test.support.AssertionErrors.*;
import static org.springframework.ws.test.support.AssertionErrors.assertTrue;
import org.custommonkey.xmlunit.Diff;
import org.springframework.ws.WebServiceMessage;
@@ -26,6 +26,7 @@ import org.springframework.ws.WebServiceMessage;
*
* @author Arjen Poutsma
* @since 2.0
* @deprecated Migrate to {@link org.springframework.ws.test.support.matcher.xmlunit2.DiffMatcher}.
*/
public abstract class DiffMatcher implements WebServiceMessageMatcher {

View File

@@ -16,7 +16,7 @@
package org.springframework.ws.test.support.matcher;
import static org.springframework.ws.test.support.AssertionErrors.*;
import static org.springframework.ws.test.support.AssertionErrors.fail;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
@@ -34,6 +34,7 @@ import org.w3c.dom.Document;
* @author Arjen Poutsma
* @author Lukas Krecan
* @since 2.0
* @deprecated Migrate to {@link org.springframework.ws.test.support.matcher.xmlunit2.PayloadDiffMatcher}.s
*/
public class PayloadDiffMatcher extends DiffMatcher {

View File

@@ -16,7 +16,8 @@
package org.springframework.ws.test.support.matcher;
import static org.springframework.ws.test.support.AssertionErrors.*;
import static org.springframework.ws.test.support.AssertionErrors.assertTrue;
import static org.springframework.ws.test.support.AssertionErrors.fail;
import java.io.IOException;
@@ -36,6 +37,7 @@ import org.w3c.dom.Document;
*
* @author Alexander Shutyaev
* @since 2.1.1
* @deprecated Migrate to {@link org.springframework.ws.test.support.matcher.xmlunit2.SoapEnvelopeDiffMatcher}.
*/
public class SoapEnvelopeDiffMatcher extends AbstractSoapMessageMatcher {

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2005-2022 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.test.support.matcher.xmlunit2;
import static org.springframework.ws.test.support.AssertionErrors.assertTrue;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.test.support.matcher.WebServiceMessageMatcher;
import org.xmlunit.diff.Diff;
/**
* Implementation of {@link WebServiceMessageMatcher} based on XMLUnit's {@link Diff}.
*
* @author Greg Turnquist
* @since 3.1
*/
public abstract class DiffMatcher implements WebServiceMessageMatcher {
@Override
public final void match(WebServiceMessage message) throws AssertionError {
Diff diff = createDiff(message);
assertTrue("Messages are different, " + diff.toString(), !diff.hasDifferences(), "Payload",
message.getPayloadSource());
}
/**
* Creates a {@link Diff} for the given message.
*
* @param message the message
* @return the diff
* @throws Exception in case of errors
*/
protected abstract Diff createDiff(WebServiceMessage message);
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2005-2022 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.test.support.matcher.xmlunit2;
import static org.springframework.ws.test.support.AssertionErrors.fail;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMResult;
import org.springframework.util.Assert;
import org.springframework.ws.WebServiceMessage;
import org.springframework.xml.transform.TransformerHelper;
import org.w3c.dom.Document;
import org.xmlunit.builder.DiffBuilder;
import org.xmlunit.diff.Diff;
/**
* Matches {@link Source} payloads.
*
* @author Greg Turnquist
* @since 3.1
*/
public class PayloadDiffMatcher extends DiffMatcher {
private final Source expected;
private final TransformerHelper transformerHelper = new TransformerHelper();
public PayloadDiffMatcher(Source expected) {
Assert.notNull(expected, "'expected' must not be null");
this.expected = expected;
}
@Override
protected final Diff createDiff(WebServiceMessage message) {
Source payload = message.getPayloadSource();
if (payload == null) {
fail("Request message does not contain payload");
}
return createDiff(payload);
}
protected Diff createDiff(Source payload) {
Document expectedDocument = createDocumentFromSource(expected);
Document actualDocument = createDocumentFromSource(payload);
return DiffBuilder.compare(expectedDocument) //
.withTest(actualDocument) //
.checkForSimilar() //
.build();
}
private Document createDocumentFromSource(Source source) {
try {
DOMResult result = new DOMResult();
transformerHelper.transform(source, result);
return (Document) result.getNode();
} catch (TransformerException ex) {
fail("Could not transform source to DOMResult" + ex.getMessage());
return null;
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2005-2022 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.test.support.matcher.xmlunit2;
import static org.springframework.ws.test.support.AssertionErrors.assertTrue;
import static org.springframework.ws.test.support.AssertionErrors.fail;
import java.io.IOException;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMResult;
import org.springframework.util.Assert;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.ws.test.support.matcher.AbstractSoapMessageMatcher;
import org.springframework.xml.transform.TransformerHelper;
import org.w3c.dom.Document;
import org.xmlunit.builder.DiffBuilder;
import org.xmlunit.diff.Diff;
/**
* Matches {@link Source} SOAP envelopes.
*
* @author Greg Turnquist
* @since 3.1
*/
public class SoapEnvelopeDiffMatcher extends AbstractSoapMessageMatcher {
private final Source expected;
private final TransformerHelper transformerHelper = new TransformerHelper();
public SoapEnvelopeDiffMatcher(Source expected) {
Assert.notNull(expected, "'expected' must not be null");
this.expected = expected;
}
@Override
protected void match(SoapMessage soapMessage) throws IOException, AssertionError {
Document actualDocument = soapMessage.getDocument();
Document expectedDocument = createDocumentFromSource(expected);
Diff diff = DiffBuilder.compare(expectedDocument).ignoreWhitespace().withTest(actualDocument).checkForSimilar()
.build();
assertTrue("Envelopes are different, " + diff.toString(), !diff.hasDifferences());
}
private Document createDocumentFromSource(Source source) {
try {
DOMResult result = new DOMResult();
transformerHelper.transform(source, result);
return (Document) result.getNode();
} catch (TransformerException ex) {
fail("Could not transform source to DOMResult" + ex.getMessage());
return null;
}
}
}

View File

@@ -16,16 +16,33 @@
package org.springframework.ws.test.client;
import static org.assertj.core.api.Assertions.*;
import static org.easymock.EasyMock.*;
import static org.springframework.ws.test.client.RequestMatchers.*;
import static org.springframework.ws.test.client.ResponseCreators.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.springframework.ws.test.client.RequestMatchers.anything;
import static org.springframework.ws.test.client.RequestMatchers.connectionTo;
import static org.springframework.ws.test.client.RequestMatchers.payload;
import static org.springframework.ws.test.client.RequestMatchers.soapEnvelope;
import static org.springframework.ws.test.client.RequestMatchers.soapHeader;
import static org.springframework.ws.test.client.RequestMatchers.validPayload;
import static org.springframework.ws.test.client.RequestMatchers.xpath;
import static org.springframework.ws.test.client.ResponseCreators.withClientOrSenderFault;
import static org.springframework.ws.test.client.ResponseCreators.withPayload;
import static org.springframework.ws.test.client.ResponseCreators.withSoapEnvelope;
import java.net.URI;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.transform.Source;
@@ -35,6 +52,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import org.springframework.ws.soap.SoapMessage;
@@ -329,6 +347,34 @@ public class MockWebServiceServerTest {
});
}
@Test
public void soapEnvelopeMatch() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(EnvelopeMatcherRequest.class, EnvelopeMatcherResponse.class);
template = new WebServiceTemplate(marshaller);
template.setDefaultUri("https://example.com");
server = MockWebServiceServer.createServer(template);
Source expectedSoapRequest = new StringSource(
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">" + "<SOAP-ENV:Header/>"
+ "<SOAP-ENV:Body>" + "<EnvelopeMatcherRequest>" + "<myData>123456</myData>" + "</EnvelopeMatcherRequest>"
+ "</SOAP-ENV:Body>" + "</SOAP-ENV:Envelope>");
Source soapResponse = new StringSource(
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">" + "<SOAP-ENV:Body>"
+ "<EnvelopeMatcherResponse>" + "<myData>654321</myData>" + "</EnvelopeMatcherResponse>"
+ "</SOAP-ENV:Body>" + "</SOAP-ENV:Envelope>");
server.expect(soapEnvelope(expectedSoapRequest)).andRespond(withSoapEnvelope(soapResponse));
EnvelopeMatcherRequest request = new EnvelopeMatcherRequest();
request.setMyData("123456");
assertThat(request.getMyData()).isEqualTo("123456");
EnvelopeMatcherResponse response = (EnvelopeMatcherResponse) template.marshalSendAndReceive(request);
assertThat(response.getMyData()).isEqualTo("654321");
}
@Test
public void verifyFailure() {
@@ -358,7 +404,35 @@ public class MockWebServiceServerTest {
});
}
public static class MyClient extends WebServiceGatewaySupport {
static class MyClient extends WebServiceGatewaySupport {
}
@XmlRootElement(name = "EnvelopeMatcherRequest")
private static class EnvelopeMatcherRequest {
private String myData;
public String getMyData() {
return myData;
}
public void setMyData(String myData) {
this.myData = myData;
}
}
@XmlRootElement(name = "EnvelopeMatcherResponse")
private static class EnvelopeMatcherResponse {
private String myData;
public String getMyData() {
return myData;
}
public void setMyData(String myData) {
this.myData = myData;
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2005-2022 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.test.support.matcher.xmlunit2;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import javax.xml.soap.MessageFactory;
import org.junit.jupiter.api.Test;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.ws.soap.saaj.SaajSoapMessage;
import org.springframework.xml.transform.StringSource;
public class PayloadDiffMatcherTest {
@Test
public void match() {
String xml = "<element xmlns='http://example.com'/>";
WebServiceMessage message = createMock(WebServiceMessage.class);
expect(message.getPayloadSource()).andReturn(new StringSource(xml)).times(2);
replay(message);
PayloadDiffMatcher matcher = new PayloadDiffMatcher(new StringSource(xml));
matcher.match(message);
verify(message);
}
@Test
public void nonMatch() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> {
String actual = "<element1 xmlns='http://example.com'/>";
WebServiceMessage message = createMock(WebServiceMessage.class);
expect(message.getPayloadSource()).andReturn(new StringSource(actual)).times(2);
replay(message);
String expected = "<element2 xmlns='http://example.com'/>";
PayloadDiffMatcher matcher = new PayloadDiffMatcher(new StringSource(expected));
matcher.match(message);
});
}
@Test
public void noPayload() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> {
PayloadDiffMatcher matcher = new PayloadDiffMatcher(new StringSource("<message/>"));
MessageFactory messageFactory = MessageFactory.newInstance();
SoapMessage soapMessage = new SaajSoapMessage(messageFactory.createMessage());
matcher.createDiff(soapMessage);
});
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2005-2022 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.test.support.matcher.xmlunit2;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import javax.xml.transform.dom.DOMResult;
import org.junit.jupiter.api.Test;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.xml.transform.StringSource;
import org.springframework.xml.transform.TransformerHelper;
import org.w3c.dom.Document;
public class SoapEnvelopeDiffMatcherTest {
@Test
public void match() throws Exception {
String xml = "<?xml version='1.0'?>" + "<soap:Envelope xmlns:soap='http://www.w3.org/2003/05/soap-envelope'>"
+ "<soap:Header><header xmlns='http://example.com'/></soap:Header>"
+ "<soap:Body><payload xmlns='http://example.com'/></soap:Body>" + "</soap:Envelope>";
DOMResult result = new DOMResult();
TransformerHelper transformerHelper = new TransformerHelper();
transformerHelper.transform(new StringSource(xml), result);
SoapMessage message = createMock(SoapMessage.class);
expect(message.getDocument()).andReturn((Document) result.getNode()).once();
replay(message);
SoapEnvelopeDiffMatcher matcher = new SoapEnvelopeDiffMatcher(new StringSource(xml));
matcher.match(message);
verify(message);
}
@Test
public void nonMatch() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> {
StringBuilder xmlBuilder = new StringBuilder();
xmlBuilder.append("<?xml version='1.0'?>");
xmlBuilder.append("<soap:Envelope xmlns:soap='http://www.w3.org/2003/05/soap-envelope'>");
xmlBuilder.append("<soap:Header><header xmlns='http://example.com'/></soap:Header>");
xmlBuilder.append("<soap:Body><payload%s xmlns='http://example.com'/></soap:Body>");
xmlBuilder.append("</soap:Envelope>");
String xml = xmlBuilder.toString();
String actual = String.format(xml, "1");
DOMResult result = new DOMResult();
TransformerHelper transformerHelper = new TransformerHelper();
transformerHelper.transform(new StringSource(actual), result);
SoapMessage message = createMock(SoapMessage.class);
expect(message.getDocument()).andReturn((Document) result.getNode()).once();
replay(message);
String expected = String.format(xml, "2");
SoapEnvelopeDiffMatcher matcher = new SoapEnvelopeDiffMatcher(new StringSource(expected));
matcher.match(message);
});
}
}