You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ja...@apache.org on 2020/10/29 08:44:49 UTC
[camel-quarkus] 01/02: Add WireMock test support
This is an automated email from the ASF dual-hosted git repository.
jamesnetherton pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit 796fe87c1d84880c57651c50176a40ca79191a5d
Author: James Netherton <ja...@gmail.com>
AuthorDate: Wed Oct 28 08:52:58 2020 +0000
Add WireMock test support
---
integration-tests-support/pom.xml | 1 +
integration-tests-support/wiremock/README.adoc | 48 +++++
integration-tests-support/wiremock/pom.xml | 62 ++++++
.../camel/quarkus/test/wiremock/MockServer.java | 32 ++++
.../WireMockTestResourceLifecycleManager.java | 211 +++++++++++++++++++++
pom.xml | 1 +
poms/bom-test/pom.xml | 28 +++
7 files changed, 383 insertions(+)
diff --git a/integration-tests-support/pom.xml b/integration-tests-support/pom.xml
index cd67962..8c8ad16 100644
--- a/integration-tests-support/pom.xml
+++ b/integration-tests-support/pom.xml
@@ -45,6 +45,7 @@
<module>test-support</module>
<module>testcontainers-support</module>
<module>mock-backend</module>
+ <module>wiremock</module>
</modules>
</project>
diff --git a/integration-tests-support/wiremock/README.adoc b/integration-tests-support/wiremock/README.adoc
new file mode 100644
index 0000000..0b477e7
--- /dev/null
+++ b/integration-tests-support/wiremock/README.adoc
@@ -0,0 +1,48 @@
+== WireMock test support
+
+This module provides test support for http://wiremock.org/[WireMock]. This enables the HTTP interactions between Camel & third party services to be
+stubbed, recorded & replayed.
+
+=== Usage
+
+Add the following test scoped dependency into the extension integration test pom.xml:
+
+[source,xml]
+----
+<dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-integration-wiremock-support</artifactId>
+ <scope>test</scope>
+</dependency>
+----
+
+Next create a class that extends the abstract `WireMockTestResourceLifecycleManager`. You'll need to implement abstract methods:
+
+* `getRecordTargetBaseUrl` - To specify the base URL of the service interactions to be recorded
+* `isMockingEnabled` - To determine whether the test should start the mock server or invoke the real service
+
+You can also override the `start` method to perform custom initialization logic and return additional configuration properties that Camel components may need.
+
+`WireMockTestResourceLifecycleManager` sets a system property named `wiremock.url`, which is the base URL to the running WireMock server.
+In playback mode, you'll need to configure the Camel component under test to direct its API calls to this URL.
+
+==== Recording HTTP interactions
+
+The fundamentals of WireMock record and playback are documented http://wiremock.org/docs/record-playback/[here]. Setup of the `WireMockServer` is already handled by
+`WireMockTestResourceLifecycleManager`. All you need to do is ensure directory `src/test/resources/mappings` exists and to trigger recording by either:
+
+System property `-Dwiremock.record=true`
+
+Or
+
+Environment variable `WIREMOCK_RECORD=true`
+
+When all tests complete, the recorded HTTP interactions will show up in the 'mappings' directory. The recorded stub file names are quite complex, feel free
+to update them to something more human friendly.
+
+By default, stub mapping files are not saved when requests return an unsuccessful response code. You can alter this behaviour by overriding method `isDeleteRecordedMappingsOnError`.
+
+It's important to inspect the recorded files for the presence of any real API keys, secrets or passwords and replace them with made up values.
+
+WireMock generates new stub files on each recording, so it's a good idea to remove the existing contents from the 'mappings' directory
+before a recording run.
diff --git a/integration-tests-support/wiremock/pom.xml b/integration-tests-support/wiremock/pom.xml
new file mode 100644
index 0000000..320ee44
--- /dev/null
+++ b/integration-tests-support/wiremock/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-integration-tests-support</artifactId>
+ <version>1.4.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>camel-quarkus-integration-wiremock-support</artifactId>
+ <name>Camel Quarkus :: Integration Tests :: WireMock :: Support</name>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-bom-test</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-integration-test-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-integration-test-support-mock-backend</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.github.tomakehurst</groupId>
+ <artifactId>wiremock-jre8</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/MockServer.java b/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/MockServer.java
new file mode 100644
index 0000000..b6f29f1
--- /dev/null
+++ b/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/MockServer.java
@@ -0,0 +1,32 @@
+/*
+ * 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.camel.quarkus.test.wiremock;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used by the companion WireMockTestResourceLifecycleManager to inject
+ * {@link com.github.tomakehurst.wiremock.WireMockServer} instances into
+ * test classes.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface MockServer {
+}
diff --git a/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/WireMockTestResourceLifecycleManager.java b/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/WireMockTestResourceLifecycleManager.java
new file mode 100644
index 0000000..dc64c87
--- /dev/null
+++ b/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/WireMockTestResourceLifecycleManager.java
@@ -0,0 +1,211 @@
+/*
+ * 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.camel.quarkus.test.wiremock;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.recording.RecordingStatus;
+import com.github.tomakehurst.wiremock.recording.SnapshotRecordResult;
+import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import org.apache.camel.quarkus.test.mock.backend.MockBackendUtils;
+import org.jboss.logging.Logger;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.recordSpec;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+
+public abstract class WireMockTestResourceLifecycleManager implements QuarkusTestResourceLifecycleManager {
+
+ protected static final Logger LOG = Logger.getLogger(WireMockTestResourceLifecycleManager.class);
+ protected WireMockServer server;
+
+ /**
+ * Starts the {@link WireMockServer} and configures request / response recording if it has been enabled
+ */
+ @Override
+ public Map<String, String> start() {
+ Map<String, String> properties = new HashMap<>();
+
+ if (isMockingEnabled() || isRecordingEnabled()) {
+ server = createServer();
+ server.start();
+
+ if (isRecordingEnabled()) {
+ String recordTargetBaseUrl = getRecordTargetBaseUrl();
+ if (recordTargetBaseUrl != null) {
+ LOG.infof("Enabling WireMock recording for %s", recordTargetBaseUrl);
+ server.startRecording(recordSpec()
+ .forTarget(recordTargetBaseUrl)
+ .allowNonProxied(false));
+ } else {
+ throw new IllegalStateException(
+ "Must return a non-null value from getRecordTargetBaseUrl() in order to support WireMock recording");
+ }
+ }
+
+ String wireMockUrl = "http://localhost:" + server.port();
+ LOG.infof("WireMock started on %s", wireMockUrl);
+ properties.put("wiremock.url", wireMockUrl);
+ }
+
+ return properties;
+ }
+
+ /**
+ * Stops the {@link WireMockServer} instance if it was started and stops recording if record mode was enabled
+ */
+ @Override
+ public void stop() {
+ if (server != null) {
+ LOG.info("Stopping WireMockServer");
+ if (server.getRecordingStatus().getStatus().equals(RecordingStatus.Recording)) {
+ LOG.info("Stopping recording");
+ SnapshotRecordResult recordResult = server.stopRecording();
+
+ List<StubMapping> stubMappings = recordResult.getStubMappings();
+ if (isDeleteRecordedMappingsOnError()) {
+ for (StubMapping mapping : stubMappings) {
+ int status = mapping.getResponse().getStatus();
+ if (status >= 300 && mapping.shouldBePersisted()) {
+ try {
+ String fileName = mapping.getName() + "-" + mapping.getId() + ".json";
+ Path mappingFilePath = Paths.get("./src/test/resources/mappings/", fileName);
+ Files.deleteIfExists(mappingFilePath);
+ LOG.infof("Deleted mapping file %s as status code was %d", fileName, status);
+ } catch (IOException e) {
+ LOG.errorf("Failed to delete mapping file %s", e, mapping.getName());
+ }
+ }
+ }
+ }
+ }
+ server.stop();
+ }
+ }
+
+ /**
+ * If mocking is enabled, inject an instance of {@link WireMockServer} into any fields
+ * annotated with {@link MockServer}. This gives full control over creating recording rules
+ * and some aspects of the server lifecycle.
+ *
+ * The server instance is not injected if mocking is explicitly disabled, and therefore the resulting
+ * {@link MockServer} annotated field value will be null.
+ */
+ @Override
+ public void inject(Object testInstance) {
+ if (isMockingEnabled() || isRecordingEnabled()) {
+ Class<?> c = testInstance.getClass();
+ for (Field field : c.getDeclaredFields()) {
+ if (field.getAnnotation(MockServer.class) != null) {
+ if (!WireMockServer.class.isAssignableFrom(field.getType())) {
+ throw new RuntimeException("@MockServer can only be used on fields of type WireMockServer");
+ }
+
+ field.setAccessible(true);
+ try {
+ if (server == null) {
+ server = createServer();
+ server.start();
+ }
+ LOG.infof("Injecting WireMockServer for field %s", field.getName());
+ field.set(testInstance, server);
+ return;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Determines whether each of the given environment variable names is set
+ */
+ protected boolean envVarsPresent(String... envVarNames) {
+ if (envVarNames.length == 0) {
+ throw new IllegalArgumentException("envVarNames must not be empty");
+ }
+
+ boolean present = true;
+ for (String envVar : envVarNames) {
+ if (System.getenv(envVar) == null) {
+ present = false;
+ break;
+ }
+ }
+
+ return present;
+ }
+
+ /**
+ * Get the value of a given environment variable or a default value if it does not exist
+ */
+ protected String envOrDefault(String envVarName, String defaultValue) {
+ String value = System.getenv(envVarName);
+ return value != null ? value : defaultValue;
+ }
+
+ /**
+ * Whether recorded stub mapping files should be deleted if the HTTP response was an error code (>= 400).
+ *
+ * By default this returns true. Can be overridden if an error response is desired / expected from the HTTP request.
+ */
+ protected boolean isDeleteRecordedMappingsOnError() {
+ return true;
+ }
+
+ /**
+ * The target base URL that WireMock should watch for when recording requests.
+ *
+ * For example, if a test triggers an HTTP call on an external endpoint like https://api.foo.com/some/resource.
+ * Then the base URL would be https://api.foo.com
+ */
+ protected abstract String getRecordTargetBaseUrl();
+
+ /**
+ * Conditions under which the {@link WireMockServer} should be started.
+ */
+ protected abstract boolean isMockingEnabled();
+
+ /**
+ * Creates and starts a {@link WireMockServer} on a random port. {@link MockBackendUtils} triggers the log
+ * message that signifies mocking is in use.
+ */
+ private WireMockServer createServer() {
+ LOG.info("Starting WireMockServer");
+ MockBackendUtils.startMockBackend(true);
+ return new WireMockServer(options().dynamicPort());
+ }
+
+ /**
+ * Determine whether to enable WireMock record mode:
+ *
+ * http://wiremock.org/docs/record-playback/
+ */
+ private boolean isRecordingEnabled() {
+ String recordEnabled = System.getProperty("wiremock.record", System.getenv("WIREMOCK_RECORD"));
+ return recordEnabled != null && recordEnabled.equals("true");
+ }
+}
diff --git a/pom.xml b/pom.xml
index 892e91d..5274b94 100644
--- a/pom.xml
+++ b/pom.xml
@@ -119,6 +119,7 @@
<sshd.version>2.3.0</sshd.version>
<stax2.version>4.2</stax2.version>
<testcontainers.version>1.14.3</testcontainers.version>
+ <wiremock.version>2.27.2</wiremock.version>
<zt-exec.version>1.11</zt-exec.version>
<!-- Maven plugin versions (keep sorted alphabetically) -->
diff --git a/poms/bom-test/pom.xml b/poms/bom-test/pom.xml
index 1d970b6..1ffb844 100644
--- a/poms/bom-test/pom.xml
+++ b/poms/bom-test/pom.xml
@@ -98,6 +98,11 @@
<artifactId>camel-quarkus-integration-testcontainers-support</artifactId>
<version>${camel-quarkus.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-integration-wiremock-support</artifactId>
+ <version>${camel-quarkus.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.ftpserver</groupId>
@@ -166,6 +171,29 @@
</exclusions>
</dependency>
<dependency>
+ <groupId>com.github.tomakehurst</groupId>
+ <artifactId>wiremock-jre8</artifactId>
+ <version>${wiremock.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.xml.bind</groupId>
+ <artifactId>jaxb-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ </exclusion>
+ <exclusion> <!-- fix dependencyConvergence clash with junit-jupiter -->
+ <groupId>org.opentest4j</groupId>
+ <artifactId>opentest4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>zt-exec</artifactId>
<version>${zt-exec.version}</version>