You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2015/07/03 11:14:13 UTC

[18/18] isis git commit: ISIS-1166: take-on SoapEndpointPublishingRule and supporting classes

ISIS-1166: take-on SoapEndpointPublishingRule and supporting classes

(originally developed as part of https://github.com/isisaddons/isis-module-publishmq, but useful in other contexts).


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/c7280dc4
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/c7280dc4
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/c7280dc4

Branch: refs/heads/master
Commit: c7280dc40b51073b821cd3d202e316e88328bf40
Parents: 2b579e6
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Fri Jul 3 10:10:32 2015 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Fri Jul 3 10:10:32 2015 +0100

----------------------------------------------------------------------
 .../guides/_ug_testing_unit-test-support.adoc   |   1 +
 ...est-support_soap-fake-server-junit-rule.adoc | 110 +++++++++++++++++++
 .../core/unittestsupport/jaxb/JaxbMatchers.java |  52 +++++++++
 .../core/unittestsupport/jaxb/JaxbUtil.java     |  93 ++++++++++++++++
 .../soap/SoapEndpointPublishingRule.java        |  67 +++++++++++
 5 files changed, 323 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/c7280dc4/adocs/documentation/src/main/asciidoc/guides/_ug_testing_unit-test-support.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_ug_testing_unit-test-support.adoc b/adocs/documentation/src/main/asciidoc/guides/_ug_testing_unit-test-support.adoc
index 2cb5ff8..0b06e37 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_ug_testing_unit-test-support.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_ug_testing_unit-test-support.adoc
@@ -9,5 +9,6 @@ Isis Core provides a number of unit test helpers for you to use (if you wish) to
 
 include::_ug_testing_unit-test-support_contract-tests.adoc[leveloffset=+1]
 include::_ug_testing_unit-test-support_jmock-extensions.adoc[leveloffset=+1]
+include::_ug_testing_unit-test-support_soap-fake-server-junit-rule.adoc[leveloffset=+1]
 include::_ug_testing_unit-test-support_maven-configuration.adoc[leveloffset=+1]
 

http://git-wip-us.apache.org/repos/asf/isis/blob/c7280dc4/adocs/documentation/src/main/asciidoc/guides/_ug_testing_unit-test-support_soap-fake-server-junit-rule.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_ug_testing_unit-test-support_soap-fake-server-junit-rule.adoc b/adocs/documentation/src/main/asciidoc/guides/_ug_testing_unit-test-support_soap-fake-server-junit-rule.adoc
new file mode 100644
index 0000000..6dad06b
--- /dev/null
+++ b/adocs/documentation/src/main/asciidoc/guides/_ug_testing_unit-test-support_soap-fake-server-junit-rule.adoc
@@ -0,0 +1,110 @@
+[[_ug_testing_unit-test-support_soap-fake-server-junit-rule]]
+= SOAP Fake Servers
+:Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you 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.
+:_basedir: ../
+:_imagesdir: images/
+
+
+
+No man is an island, and neither are most applications.  Chances are that at some point you may need to integrate your Apache Isis application to other external systems, possibly using old-style SOAP web services.  The SOAP client in this case could be a domain service within your app, or it might be externalized, eg invoked through a scheduler or using link:http://camel.apache.org[Apache Camel].
+
+While you will want to (of course) perform manual system testing/UAT with a test instance of that external system, it's also useful to be able to perform unit testing of your SOAP client component.
+
+The `SoapEndpointPublishingRule` is a simple JUnit rule that allows you to run a fake SOAP endpoint within an unit test.
+
+[TIP]
+====
+The (non-ASF) http://github.com/isisaddons/isis-module-publishmq[Isis addons' publishmq] module provides a full example of how to integrate and test an Apache Isis application with a (faked out) external system.
+====
+
+
+== `SoapEndpointPublishingRule`
+
+The idea behing this rule is that you write a fake server that implements the same WSDL contract as the "real" external system does, but which also exposes additional API to specify responses (or throw exceptions) from SOAP calls.  It also typically records the requests and allows these to be queried.
+
+In its setup your unit test instantiates the fake server, and gets the rule to host that fake server on an SOAP endpoint.  It also instantiates the SOAP client, pointing it at the endpoint address (that is, a URL) that the fake server is runnig on.
+
+In the test methods your unit test sets up expectations on your fake server, and then exercises the SOAP client.  The SOAP client calls the fake server, which then responds accordingly.  The test can then assert that all expected interactions have occurred.
+
+So that tests don't take too long to run, the rule puts the server onto a thread-local.  Therefore the unit tests should clear up any state on the fake server.
+
+The easiest way to use the rule is to subclass it for your particular fake server, eg:
+
+[source,java]
+----
+public class ExternalSystemFakeServerRule
+        extends SoapEndpointPublishingRule<ExternalSystemFakeServer> {  <1>
+    public ExternalSystemFakeServerRule() {
+        this(54345);                                                    <2>
+    }
+    public ExternalSystemFakeServerRule(final int port) {
+        super(
+            buildAddress(port),                                         <3>
+            () -> new ExternalSystemFakeServer());                      <4>
+    }
+    protected static String buildAddress(final int port) {
+        return String.format("http://localhost:%d/any/old/string/will/work", port);
+    }
+}
+----
+<1> generic type specifies the class that implements the endpoint
+<2> hardcoded port (or could choose one at random from within a range)
+<3> build an address based on the port
+<4> provide a `Supplier` that will instantiate the fake server
+
+
+Your unit test should then look something like:
+
+[source,java]
+----
+public class ExternalSystemFakeServerRuleTest {
+    @Rule
+    public ExternalSystemFakeServerRule serverRule = new ExternalSystemFakeServerRule();
+    private ExternalSystemFakeServer externalSystemFakeServer;
+    private DemoObject externalSystemContract;                                      // <1>
+    @Before
+    public void setUp() throws Exception {
+        final DemoObjectService externalSystemService =                             // <2>
+                new DemoObjectService(ExternalSystemWsdl.getWsdl());                // <3>
+        externalSystemContract = externalSystemService.getDemoObjectOverSOAP();
+        BindingProvider provider = (BindingProvider) externalSystemContract;
+        provider.getRequestContext().put(
+                BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
+                serverRule.getEndpointAddress()
+        );
+        externalSystemFakeServer = serverRule.getPublishedEndpoint();               // <4>
+    }
+    @Test
+    public void happy_case() throws Exception {
+        // given
+        final Update update = new Update();                                         // <5>
+        ...
+
+        // expect
+        final UpdateResponse response = new UpdateResponse();                       // <6>
+        ...
+        externalSystemFakeServer.control().setResponse(updateResponse);
+
+        // when
+        PostResponse response = externalSystemContract.post(update);                // <7>
+
+        // then
+        final List<Update> updates =                                                // <8>
+            externalSystemFakeServer.control().getUpdates();
+        ...
+    }
+    ...
+}
+----
+<1> the SOAP contract as defined in WSDL and generated by wsdl2java
+<2> also generated by wsdl2java
+<3> `getWsdl()` is a utility method to return a URL for the WSDL (eg from the classpath)
+<4> get hold of the fake server running at the end point
+<5> create a request (generated from the WSDL and wsdl2java)
+<6> instruct fake server how to respond
+<7> invoke the service
+<8> check service was correctly invoked etc.
+
+== XML Marshalling Support
+
+Apache Isis' unit testing support also provides helper `JaxbUtil` and `JaxbMatchers` classes.  These are useful if you have exampler XML-serialized representations of the SOAP requests and response payloads and want to use these within your tests.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/c7280dc4/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jaxb/JaxbMatchers.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jaxb/JaxbMatchers.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jaxb/JaxbMatchers.java
new file mode 100644
index 0000000..6299d3d
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jaxb/JaxbMatchers.java
@@ -0,0 +1,52 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.isis.core.unittestsupport.jaxb;
+
+import com.google.common.base.Objects;
+
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * For example usage, see <a href="https://github.com/isisaddons/isis-module-publishmq">Isis addons' publishmq module</a> (non-ASF)
+ */
+public class JaxbMatchers {
+
+    private JaxbMatchers(){}
+
+    /**
+     * Performs an equality comparison of a {@link javax.xml.bind.annotation.XmlRootElement}-annotated class
+     * to another by converting into XML first.
+     */
+    public static <T> Matcher<? super T> isEquivalentTo(final T expected) {
+        return new TypeSafeMatcher<T>() {
+            @Override
+            protected boolean matchesSafely(final T item) {
+                final String expectedXml = JaxbUtil.toXml(expected);
+                final String itemXml = JaxbUtil.toXml(item);
+                return Objects.equal(expectedXml, itemXml);
+            }
+
+            @Override
+            public void describeTo(final org.hamcrest.Description description) {
+                final String expectedXml = JaxbUtil.toXml(expected);
+                description.appendText("is equivalent to ").appendValue(expectedXml);
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c7280dc4/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jaxb/JaxbUtil.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jaxb/JaxbUtil.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jaxb/JaxbUtil.java
new file mode 100644
index 0000000..a4ac6a6
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jaxb/JaxbUtil.java
@@ -0,0 +1,93 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.isis.core.unittestsupport.jaxb;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.Writer;
+import java.net.URL;
+import java.nio.charset.Charset;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+
+import com.google.common.io.Resources;
+
+/**
+ * Helper methods for converting {@link javax.xml.bind.annotation.XmlRootElement}-annotated class to-and-from XML.  Intended for
+ * test use only (the {@link JAXBContext} is not cached).
+ *
+ * <p>
+ * For example usage, see <a href="https://github.com/isisaddons/isis-module-publishmq">Isis addons' publishmq module</a> (non-ASF)
+ * </p>
+ */
+public class JaxbUtil {
+
+    private JaxbUtil(){}
+
+    public static <T> T fromXml(
+            final Reader reader,
+            final Class<T> dtoClass) {
+        Unmarshaller un = null;
+        try {
+            un = getJaxbContext(dtoClass).createUnmarshaller();
+            return (T) un.unmarshal(reader);
+        } catch (JAXBException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T fromXml(
+            final Class<?> contextClass,
+            final String resourceName,
+            final Charset charset,
+            final Class<T> dtoClass) throws IOException {
+        final URL url = Resources.getResource(contextClass, resourceName);
+        final String s = Resources.toString(url, charset);
+        return fromXml(new StringReader(s), dtoClass);
+    }
+
+    public static <T> String toXml(final T dto) {
+        final CharArrayWriter caw = new CharArrayWriter();
+        toXml(dto, caw);
+        return caw.toString();
+    }
+
+    public static <T> void toXml(final T dto, final Writer writer) {
+        Marshaller m = null;
+        try {
+            final Class<?> aClass = dto.getClass();
+            m = getJaxbContext(aClass).createMarshaller();
+            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+            m.marshal(dto, writer);
+        } catch (JAXBException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static JAXBContext getJaxbContext(Class<?> dtoClass) {
+        try {
+            return JAXBContext.newInstance(dtoClass);
+        } catch (JAXBException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c7280dc4/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/soap/SoapEndpointPublishingRule.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/soap/SoapEndpointPublishingRule.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/soap/SoapEndpointPublishingRule.java
new file mode 100644
index 0000000..f6b4385
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/soap/SoapEndpointPublishingRule.java
@@ -0,0 +1,67 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.isis.core.unittestsupport.soap;
+
+import java.util.function.Supplier;
+
+import javax.xml.ws.Endpoint;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+
+/**
+ * For example usage, see <a href="https://github.com/isisaddons/isis-module-publishmq">Isis addons' publishmq module</a> (non-ASF)
+ */
+public class SoapEndpointPublishingRule<T> implements TestRule {
+
+    private static ThreadLocal<Object> ENDPOINT = new ThreadLocal<>();
+
+    private final String endpointAddress;
+    private final Supplier<T> endpointImplementorFactory;
+
+    public SoapEndpointPublishingRule(final String endpointAddress, final Supplier<T> endpointImplementorFactory) {
+        this.endpointAddress = endpointAddress;
+        this.endpointImplementorFactory = endpointImplementorFactory;
+    }
+
+    public String getEndpointAddress() {
+        return endpointAddress;
+    }
+
+    @Override
+    public Statement apply(final Statement base, final Description description) {
+        publishEndpointIfRequired();
+        return base;
+    }
+
+    private void publishEndpointIfRequired() {
+        if (ENDPOINT.get() == null) {
+            final T implementor = endpointImplementorFactory.get();
+            Endpoint.publish(endpointAddress, implementor);
+            ENDPOINT.set(implementor);
+        }
+    }
+    //endregion
+
+    public T getPublishedEndpoint() {
+        return (T) ENDPOINT.get();
+    }
+
+
+}