You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2022/01/26 13:03:47 UTC

[camel-examples] branch main updated: CAMEL-11834: Add tests for flight-recorder/ftp/java8/jdbc/jms-file/jmx/jooq/kamelet/couchbase/kafka/mongodb/jetty/OAIPMH (#56)

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-examples.git


The following commit(s) were added to refs/heads/main by this push:
     new d2488c9  CAMEL-11834: Add tests for flight-recorder/ftp/java8/jdbc/jms-file/jmx/jooq/kamelet/couchbase/kafka/mongodb/jetty/OAIPMH (#56)
d2488c9 is described below

commit d2488c9e1c838da40a0a4990e645f3806f05feb6
Author: Nicolas Filotto <es...@users.noreply.github.com>
AuthorDate: Wed Jan 26 14:03:41 2022 +0100

    CAMEL-11834: Add tests for flight-recorder/ftp/java8/jdbc/jms-file/jmx/jooq/kamelet/couchbase/kafka/mongodb/jetty/OAIPMH (#56)
    
    * CAMEL-11834: Add tests for flight-recorder/ftp/java8/jdbc/jms-file/jmx/jooq/kamelet
    
    * CAMEL-11834: Add test for couchbase-log
    
    * CAMEL-11834: Add test for kafka
    
    * CAMEL-11834: Add test for MongoDB
    
    * CAMEL-11834: Add test for Jetty
    
    * CAMEL-11834: Add test for OAIPMH
---
 examples/couchbase-log/README.adoc                 |  16 ++-
 examples/couchbase-log/pom.xml                     |  13 ++-
 .../src/main/resources/application.properties      |   2 +-
 .../org/apache/camel/example/CouchbaseTest.java    | 128 +++++++++++++++++++++
 examples/debezium/README.adoc                      |   2 +-
 .../debezium/DebeziumMySqlConsumerToKinesis.java   |  16 +--
 .../debezium/KinesisProducerToCassandra.java       |  14 +--
 examples/flight-recorder/pom.xml                   |   7 ++
 .../apache/camel/example/FlightRecorderTest.java   |  48 ++++++++
 examples/ftp/README.adoc                           |   7 +-
 examples/ftp/pom.xml                               |  12 ++
 examples/ftp/src/main/resources/ftp.properties     |  15 ++-
 .../java/org/apache/camel/example/ftp/FtpTest.java | 125 ++++++++++++++++++++
 .../src/test/resources/users.properties}           |   5 +-
 examples/hazelcast-kubernetes/README.adoc          |   2 +-
 examples/java8/pom.xml                             |   6 +
 .../apache/camel/example/java8/MyApplication.java  |   7 +-
 .../org/apache/camel/example/java8/Java8Test.java  |  46 ++++++++
 examples/jdbc/README.adoc                          |  12 +-
 examples/jdbc/pom.xml                              |   6 +
 .../org/apache/camel/example/jdbc/JdbcTest.java    |  48 ++++++++
 examples/jms-file/README.adoc                      |   4 +-
 examples/jms-file/pom.xml                          |  18 ++-
 .../example/jmstofile/CamelJmsToFileExample.java   |  36 +++---
 .../camel/example/jmstofile/JmsToFileTest.java     |  73 ++++++++++++
 examples/jmx/pom.xml                               |   6 +
 .../java/org/apache/camel/example/jmx/JMXTest.java |  63 ++++++++++
 examples/jooq/README.adoc                          |   4 +-
 examples/jooq/pom.xml                              |   7 ++
 .../org/apache/camel/examples/jooq/JOOQTest.java   |  49 ++++++++
 examples/kafka/README.adoc                         |  47 +++++---
 examples/kafka/pom.xml                             |  12 ++
 .../camel/example/kafka/MessageConsumerClient.java |  44 ++++---
 .../example/kafka/MessagePublisherClient.java      |  77 +++++++------
 .../src/main/resources/application.properties      |   2 +-
 .../org/apache/camel/example/kafka/KafkaTest.java  |  97 ++++++++++++++++
 examples/kamelet/pom.xml                           |   7 +-
 .../java/org/apache/camel/example/KameletTest.java |  46 ++++++++
 examples/main-artemis/README.adoc                  |   9 +-
 .../org/apache/camel/example/MyConfiguration.java  |   2 +-
 .../src/main/resources/application.properties      |   2 +-
 examples/mongodb/README.adoc                       |  30 ++++-
 examples/mongodb/docker/docker-compose.yml         |   8 --
 examples/mongodb/pom.xml                           |  18 +++
 .../mongodb/MongoDBFindAllRouteBuilder.java        |   3 -
 .../example/mongodb/MongoDBInsertRouteBuilder.java |   1 -
 .../apache/camel/example/mongodb/MongoDBTest.java  | 112 ++++++++++++++++++
 examples/netty-custom-correlation/README.adoc      |  17 ++-
 examples/netty-custom-correlation/pom.xml          |   7 +-
 .../org/apache/camel/example/netty/MyClient.java   |  27 +++--
 .../org/apache/camel/example/netty/MyServer.java   |   6 +-
 .../org/apache/camel/example/netty/NettyTest.java  |  62 ++++++++++
 examples/oaipmh/README.adoc                        |  11 +-
 examples/oaipmh/pom.xml                            |   6 +
 .../apache/camel/example/oaipmh/Application.java   |  14 +--
 .../camel/example/oaipmh/OAIPMHRouteBuilder.java   |  10 +-
 .../apache/camel/example/oaipmh/OAIPMHTest.java}   |  35 +++---
 57 files changed, 1289 insertions(+), 220 deletions(-)

diff --git a/examples/couchbase-log/README.adoc b/examples/couchbase-log/README.adoc
index 7e573b4..36b4b07 100644
--- a/examples/couchbase-log/README.adoc
+++ b/examples/couchbase-log/README.adoc
@@ -4,7 +4,7 @@ This example shows how to use the Camel Main module
 to define a route from Couchbase to Log.
 
 Set your application.properties options correctly.
-You'll need also a running kafka broker.
+You'll need also a running couchbase server.
 
 === Setting up the Couchbase Container
 
@@ -12,24 +12,28 @@ First run the container
 
 [source,sh]
 ----
-docker run -d --name db -p 8091-8094:8091-8094 -p 11210:11210 couchbase:6.5.1
+docker run -d --name db -p 8091-8094:8091-8094 -p 11210:11210 couchbase:7.0.3
 ----
 
 Now go to localhost:8091 and create a new cluster with username/password admin/password
 
 Use the sample bucket beer-sample to populate the bucket.
 
-Now you're ready to run the example.
+=== Build
 
-=== How to run
-
-You can run this example using
+You will need to compile this example first:
 
 [source,sh]
 ----
 $ mvn compile
 ----
 
+Now you're ready to run the example.
+
+=== How to run
+
+You can run this example using
+
 [source,sh]
 ----
 $ mvn camel:run
diff --git a/examples/couchbase-log/pom.xml b/examples/couchbase-log/pom.xml
index 1fd8cc8..606903d 100644
--- a/examples/couchbase-log/pom.xml
+++ b/examples/couchbase-log/pom.xml
@@ -77,7 +77,18 @@
             <artifactId>logback-classic</artifactId>
             <version>${logback-version}</version>
         </dependency>
-
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>couchbase</artifactId>
+            <version>${testcontainers-version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/examples/couchbase-log/src/main/resources/application.properties b/examples/couchbase-log/src/main/resources/application.properties
index 4c061b8..a3e3914 100644
--- a/examples/couchbase-log/src/main/resources/application.properties
+++ b/examples/couchbase-log/src/main/resources/application.properties
@@ -17,7 +17,7 @@
 
 # to configure camel main
 # here you can configure options on camel main (see MainConfigurationProperties class)
-camel.main.name = Kafka-to-Azure-Storage-Blob
+camel.main.name = LogCouchbase
 
 couchbase.host=localhost
 couchbase.port=8091
diff --git a/examples/couchbase-log/src/test/java/org/apache/camel/example/CouchbaseTest.java b/examples/couchbase-log/src/test/java/org/apache/camel/example/CouchbaseTest.java
new file mode 100644
index 0000000..443471b
--- /dev/null
+++ b/examples/couchbase-log/src/test/java/org/apache/camel/example/CouchbaseTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.example;
+
+import com.couchbase.client.java.Bucket;
+import com.couchbase.client.java.Cluster;
+import com.couchbase.client.java.json.JsonObject;
+import com.couchbase.client.java.manager.view.DesignDocument;
+import com.couchbase.client.java.manager.view.View;
+import com.couchbase.client.java.view.DesignDocumentNamespace;
+import org.apache.camel.CamelContext;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.component.couchbase.CouchbaseConstants;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.couchbase.BucketDefinition;
+import org.testcontainers.couchbase.CouchbaseContainer;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.camel.util.PropertiesHelper.asProperties;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A unit test checking that Camel consume data from Couchbase.
+ */
+class CouchbaseTest extends CamelTestSupport {
+
+    private static final String IMAGE = "couchbase/server:7.0.3";
+    private static final String BUCKET = "test-bucket-" + System.currentTimeMillis();
+    private static CouchbaseContainer CONTAINER;
+    private static Cluster CLUSTER;
+
+    @BeforeAll
+    static void init() {
+        CONTAINER = new CouchbaseContainer(IMAGE) {
+            {
+                // Camel component tries to use the default port of the KV Service, so we need to fix it
+                final int kvPort = 11210;
+                addFixedExposedPort(kvPort, kvPort);
+            }
+        }.withBucket(new BucketDefinition(BUCKET));
+        CONTAINER.start();
+        CLUSTER = Cluster.connect(
+            CONTAINER.getConnectionString(),
+            CONTAINER.getUsername(),
+            CONTAINER.getPassword()
+        );
+        DesignDocument designDoc = new DesignDocument(
+            CouchbaseConstants.DEFAULT_DESIGN_DOCUMENT_NAME,
+            Collections.singletonMap(
+                CouchbaseConstants.DEFAULT_VIEWNAME,
+                new View("function (doc, meta) {  emit(meta.id, doc);}")
+            )
+        );
+        CLUSTER.bucket(BUCKET).viewIndexes().upsertDesignDocument(designDoc, DesignDocumentNamespace.PRODUCTION);
+    }
+
+    @AfterAll
+    static void destroy() {
+        if (CONTAINER != null) {
+            try {
+                if (CLUSTER != null) {
+                    CLUSTER.disconnect();
+                }
+            } finally {
+                CONTAINER.stop();
+            }
+        }
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext camelContext = super.createCamelContext();
+        camelContext.getPropertiesComponent().setLocation("classpath:application.properties");
+        return camelContext;
+    }
+
+    @Override
+    protected Properties useOverridePropertiesWithPropertiesComponent() {
+        return asProperties(
+            "couchbase.host", CONTAINER.getHost(),
+            "couchbase.port", Integer.toString(CONTAINER.getBootstrapHttpDirectPort()),
+            "couchbase.username", CONTAINER.getUsername(),
+            "couchbase.password", CONTAINER.getPassword(),
+            "couchbase.bucket", BUCKET
+        );
+    }
+
+    @Test
+    void should_consume_bucket() {
+        Bucket bucket = CLUSTER.bucket(BUCKET);
+        bucket.waitUntilReady(Duration.ofSeconds(10L));
+        for (int i = 0; i < 10; i++) {
+            bucket.defaultCollection().upsert("my-doc-" + i, JsonObject.create().put("name", "My Name " + i));
+        }
+
+        NotifyBuilder notify = new NotifyBuilder(context).whenCompleted(10).wereSentTo("log:info").create();
+        assertTrue(
+            notify.matches(20, TimeUnit.SECONDS), "10 messages should be completed"
+        );
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new MyRouteBuilder();
+    }
+}
diff --git a/examples/debezium/README.adoc b/examples/debezium/README.adoc
index ae0c9a4..b496bab 100644
--- a/examples/debezium/README.adoc
+++ b/examples/debezium/README.adoc
@@ -80,7 +80,7 @@ Run the Kinesis producer first
 $ mvn compile exec:java -Pkinesis-producer
 ----
 
-Run the Debezium consumer in the seperate shell
+Run the Debezium consumer in the separate shell
 
 [source,sh]
 ----
diff --git a/examples/debezium/src/main/java/org/apache/camel/example/debezium/DebeziumMySqlConsumerToKinesis.java b/examples/debezium/src/main/java/org/apache/camel/example/debezium/DebeziumMySqlConsumerToKinesis.java
index 6c99c64..e97b277 100644
--- a/examples/debezium/src/main/java/org/apache/camel/example/debezium/DebeziumMySqlConsumerToKinesis.java
+++ b/examples/debezium/src/main/java/org/apache/camel/example/debezium/DebeziumMySqlConsumerToKinesis.java
@@ -35,8 +35,8 @@ public final class DebeziumMySqlConsumerToKinesis {
 
     private static final Logger LOG = LoggerFactory.getLogger(DebeziumMySqlConsumerToKinesis.class);
 
-    // use Camel Main to setup and run Camel
-    private static Main main = new Main();
+    // use Camel Main to set up and run Camel
+    private static final Main MAIN = new Main();
 
     private DebeziumMySqlConsumerToKinesis() {
     }
@@ -46,11 +46,11 @@ public final class DebeziumMySqlConsumerToKinesis {
         LOG.debug("About to run Debezium integration...");
 
         // add route
-        main.configure().addRoutesBuilder(new RouteBuilder() {
+        MAIN.configure().addRoutesBuilder(new RouteBuilder() {
             public void configure() {
-                // Initial Debezium route that will run and listens to the changes,
-                // first it will perform an initial snapshot using (select * from) in case there are no offsets
-                // exists for the connector and then it will listens to MySQL binlogs for any DB events such as (UPDATE, INSERT and DELETE)
+                // Initial Debezium route that will run and listen to the changes,
+                // first it will perform an initial snapshot using (select * from) in case no offset
+                // exists for the connector, and then it will listen to MySQL binlogs for any DB events such as (UPDATE, INSERT and DELETE)
                 from("debezium-mysql:{{debezium.mysql.name}}?"
                         + "databaseServerId={{debezium.mysql.databaseServerId}}"
                         + "&databaseHostname={{debezium.mysql.databaseHostName}}"
@@ -74,7 +74,7 @@ public final class DebeziumMySqlConsumerToKinesis {
                             final Map value = exchange.getMessage().getBody(Map.class);
                             // Also, we need the operation in order to determine when an INSERT, UPDATE or DELETE happens
                             final String operation = (String) exchange.getMessage().getHeader(DebeziumConstants.HEADER_OPERATION);
-                            // We we will put everything as nested Map in order to utilize Camel's Type Format
+                            // We will put everything as nested Map in order to utilize Camel's Type Format
                             final Map<String, Object> kinesisBody = new HashMap<>();
 
                             kinesisBody.put("key", key);
@@ -101,7 +101,7 @@ public final class DebeziumMySqlConsumerToKinesis {
         });
 
         // start and run Camel (block)
-        main.run();
+        MAIN.run();
     }
 
 }
diff --git a/examples/debezium/src/main/java/org/apache/camel/example/debezium/KinesisProducerToCassandra.java b/examples/debezium/src/main/java/org/apache/camel/example/debezium/KinesisProducerToCassandra.java
index e4094ad..02185c6 100644
--- a/examples/debezium/src/main/java/org/apache/camel/example/debezium/KinesisProducerToCassandra.java
+++ b/examples/debezium/src/main/java/org/apache/camel/example/debezium/KinesisProducerToCassandra.java
@@ -33,8 +33,8 @@ public final class KinesisProducerToCassandra {
 
     private static final Logger LOG = LoggerFactory.getLogger(KinesisProducerToCassandra.class);
 
-    // use Camel Main to setup and run Camel
-    private static Main main = new Main();
+    // use Camel Main to set up and run Camel
+    private static final Main MAIN = new Main();
 
     private KinesisProducerToCassandra() {
     }
@@ -44,7 +44,7 @@ public final class KinesisProducerToCassandra {
         LOG.debug("About to run Kinesis to Cassandra integration...");
 
         // add route
-        main.configure().addRoutesBuilder(new RouteBuilder() {
+        MAIN.configure().addRoutesBuilder(new RouteBuilder() {
             public void configure() {
                 // We set the CQL templates we need, note that an UPDATE in Cassandra means an UPSERT which is what we need
                 final String cqlUpdate = "update products set name = ?, description = ?, weight = ? where id = ?";
@@ -58,11 +58,11 @@ public final class KinesisProducerToCassandra {
                         .convertBodyTo(String.class)
                         // Unmarshal our body, it will convert it from JSON to Map
                         .unmarshal().json(JsonLibrary.Jackson)
-                        // In order not to lose the operation that we set in Debezium, we set it as a property or you can as
-                        // as well set it to a header
+                        // In order not to lose the operation that we set in Debezium, we set it as a property or as
+                        // a header
                         .setProperty("DBOperation", simple("${body[operation]}"))
                         .choice()
-                            // If we have a INSERT or UPDATE, we will need to set the body with with the CQL query parameters since we are using
+                            // If we have a INSERT or UPDATE, we will need to set the body with the CQL query parameters since we are using
                             // camel-cassandraql component
                             .when(exchangeProperty("DBOperation").in("c", "u"))
                                 .setBody(exchange -> {
@@ -100,7 +100,7 @@ public final class KinesisProducerToCassandra {
         });
 
         // start and run Camel (block)
-        main.run();
+        MAIN.run();
     }
 
 }
diff --git a/examples/flight-recorder/pom.xml b/examples/flight-recorder/pom.xml
index bda6809..791256f 100644
--- a/examples/flight-recorder/pom.xml
+++ b/examples/flight-recorder/pom.xml
@@ -91,6 +91,13 @@
             <version>${logback-version}</version>
         </dependency>
 
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>${junit-jupiter-version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/examples/flight-recorder/src/test/java/org/apache/camel/example/FlightRecorderTest.java b/examples/flight-recorder/src/test/java/org/apache/camel/example/FlightRecorderTest.java
new file mode 100644
index 0000000..6dd26b8
--- /dev/null
+++ b/examples/flight-recorder/src/test/java/org/apache/camel/example/FlightRecorderTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.example;
+
+import org.apache.camel.main.Main;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A unit test allowing to check that the flight recorder is launched as expected.
+ */
+class FlightRecorderTest {
+
+    @Test
+    void should_launch_flight_recorder() throws Exception {
+        long before = Files.list(Paths.get(".")).filter(p -> p.toString().endsWith(".jfr")).count();
+        // use Camels Main class
+        Main main = new Main();
+        // and add the routes (you can specify multiple classes)
+        main.configure().addRoutesBuilder(MyRouteBuilder.class);
+        try {
+            main.start();
+        } finally {
+            main.stop();
+        }
+        long after = Files.list(Paths.get(".")).filter(p -> p.toString().endsWith(".jfr")).count();
+        assertEquals(1L, after - before, "A flight recorder file should have been created");
+    }
+
+}
diff --git a/examples/ftp/README.adoc b/examples/ftp/README.adoc
index 80d10eb..523026d 100644
--- a/examples/ftp/README.adoc
+++ b/examples/ftp/README.adoc
@@ -50,11 +50,14 @@ $ mvn compile exec:java -Pclient
 
 To run the server you type:
 
-	mvn compile exec:java -Pserver
+[source,sh]
+----
+$ mvn compile exec:java -Pserver
+----
 
 ... and instructions will be printed on the console.
 
-You can enable verbose logging by adjustung the `src/main/resources/log4j.properties` file as documented in the file.
+You can enable verbose logging by adjusting the `src/main/resources/log4j.properties` file as documented in the file.
 
 === Help and contributions
 
diff --git a/examples/ftp/pom.xml b/examples/ftp/pom.xml
index e3f5528..d7b34c9 100644
--- a/examples/ftp/pom.xml
+++ b/examples/ftp/pom.xml
@@ -82,6 +82,18 @@
             <version>${log4j2-version}</version>
         </dependency>
 
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ftpserver</groupId>
+            <artifactId>ftpserver-core</artifactId>
+            <version>${ftpserver-version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
diff --git a/examples/ftp/src/main/resources/ftp.properties b/examples/ftp/src/main/resources/ftp.properties
index 4b42f54..9b87d1b 100644
--- a/examples/ftp/src/main/resources/ftp.properties
+++ b/examples/ftp/src/main/resources/ftp.properties
@@ -15,11 +15,22 @@
 ## limitations under the License.
 ## ---------------------------------------------------------------------------
 
+# the host of the target FTP server
+ftp.host=localhost
+# the port of the target FTP server
+ftp.port=21
+# the username to use to access to the target FTP server
+ftp.username=bob
+# the password to use to access to the target FTP server
+ftp.password=123
+# the path to access on the target FTP server
+ftp.path=/
+
 # NOTE: you may need to turn on passive mode via, passiveMode=true
-##ftp.client=ftp://changeme-to-ftp-server.com:21/mypath?autoCreate=false&username=bob&password=123
+##ftp.client=ftp://{{ftp.host}}:{{ftp.port}}{{ftp.path}}?autoCreate=false&{{ftp.username}}&password={{ftp.password}}
 
 # this example is a local FTP server
-ftp.client=ftp://localhost:21?autoCreate=false&username=bob&password=123&passiveMode=true&binary=true\
+ftp.client=ftp://{{ftp.host}}:{{ftp.port}}{{ftp.path}}?autoCreate=false&username={{ftp.username}}&password={{ftp.password}}&passiveMode=true&binary=true\
   &resumeDownload=true&localWorkDirectory=target/ftp-work\
   &transferLoggingLevel=INFO&transferLoggingIntervalSeconds=1&transferLoggingVerbose=false
 
diff --git a/examples/ftp/src/test/java/org/apache/camel/example/ftp/FtpTest.java b/examples/ftp/src/test/java/org/apache/camel/example/ftp/FtpTest.java
new file mode 100644
index 0000000..b75ecb6
--- /dev/null
+++ b/examples/ftp/src/test/java/org/apache/camel/example/ftp/FtpTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.example.ftp;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.apache.ftpserver.FtpServer;
+import org.apache.ftpserver.FtpServerFactory;
+import org.apache.ftpserver.filesystem.nativefs.NativeFileSystemFactory;
+import org.apache.ftpserver.ftplet.UserManager;
+import org.apache.ftpserver.listener.ListenerFactory;
+import org.apache.ftpserver.usermanager.ClearTextPasswordEncryptor;
+import org.apache.ftpserver.usermanager.impl.PropertiesUserManager;
+import org.apache.mina.util.AvailablePortFinder;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.camel.util.PropertiesHelper.asProperties;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A unit test checking that Camel can read/write from/to a ftp server.
+ */
+class FtpTest extends CamelTestSupport {
+
+    private static FtpServer SERVER;
+    private static int PORT;
+
+    @BeforeAll
+    static void init() throws Exception {
+        ListenerFactory factory = new ListenerFactory();
+        PORT = AvailablePortFinder.getNextAvailable();
+        // set the port of the listener
+        factory.setPort(PORT);
+        FtpServerFactory serverFactory = new FtpServerFactory();
+        // replace the default listener
+        serverFactory.addListener("default", factory.createListener());
+
+        // setup user management to read our users.properties and use clear text passwords
+        File file = new File("src/test/resources/users.properties");
+        UserManager userManager = new PropertiesUserManager(new ClearTextPasswordEncryptor(), file, "admin");
+        serverFactory.setUserManager(userManager);
+
+        NativeFileSystemFactory fsf = new NativeFileSystemFactory();
+        serverFactory.setFileSystem(fsf);
+
+        // Create the admin home
+        new File("./target/ftp-server").mkdirs();
+
+        SERVER = serverFactory.createServer();
+        // start the server
+        SERVER.start();
+    }
+
+    @AfterAll
+    static void destroy() {
+        if (SERVER != null) {
+            SERVER.stop();
+        }
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext camelContext = super.createCamelContext();
+        camelContext.getPropertiesComponent().setLocation("classpath:ftp.properties");
+        return camelContext;
+    }
+
+    @Override
+    protected Properties useOverridePropertiesWithPropertiesComponent() {
+        return asProperties(
+            "ftp.port", Integer.toString(PORT),
+            "ftp.username", "admin",
+            "ftp.password", "admin"
+        );
+    }
+
+    @Test
+    void should_download_uploaded_file() throws IOException {
+        String fileName = UUID.randomUUID().toString();
+        assertTrue(
+            new File(String.format("target/upload/%s", fileName)).createNewFile(),
+            "The test file should be created"
+        );
+        NotifyBuilder notify = new NotifyBuilder(context)
+            .whenCompleted(1).wereSentTo("ftp:*")
+            .and().whenCompleted(1).wereSentTo("file:*").create();
+
+        assertTrue(
+            notify.matches(30, TimeUnit.SECONDS), "1 file should be transferred with success"
+        );
+        assertTrue(
+            new File(String.format("target/download/%s", fileName)).exists(),
+            "The test file should be uploaded"
+        );
+    }
+
+    @Override
+    protected RoutesBuilder[] createRouteBuilders() {
+        return new RoutesBuilder[]{new MyFtpClientRouteBuilder(), new MyFtpServerRouteBuilder()};
+    }
+}
diff --git a/examples/flight-recorder/src/main/data/foo.properties b/examples/ftp/src/test/resources/users.properties
similarity index 85%
rename from examples/flight-recorder/src/main/data/foo.properties
rename to examples/ftp/src/test/resources/users.properties
index b43e6bc..d95b0c9 100644
--- a/examples/flight-recorder/src/main/data/foo.properties
+++ b/examples/ftp/src/test/resources/users.properties
@@ -15,4 +15,7 @@
 ## limitations under the License.
 ## ---------------------------------------------------------------------------
 
-bye = Bye
\ No newline at end of file
+ftpserver.user.admin
+ftpserver.user.admin.userpassword=admin
+ftpserver.user.admin.homedirectory=./target/ftp-server
+ftpserver.user.admin.writepermission=true
diff --git a/examples/hazelcast-kubernetes/README.adoc b/examples/hazelcast-kubernetes/README.adoc
index 3b304eb..b02fe1c 100644
--- a/examples/hazelcast-kubernetes/README.adoc
+++ b/examples/hazelcast-kubernetes/README.adoc
@@ -18,7 +18,7 @@ This example is based on:
 First thing you’ll need to do is preparing the environment.
 
 Once your Minikube node is up and running you’ll need to run the
-following command. In your `src/main/resources/fabric8/`` folder you’ll
+following command. In your `src/main/resources/fabric8/` folder you’ll
 find two yaml file. Run the following command using them:
 
 ....
diff --git a/examples/java8/pom.xml b/examples/java8/pom.xml
index 64df69c..7b30799 100644
--- a/examples/java8/pom.xml
+++ b/examples/java8/pom.xml
@@ -93,6 +93,12 @@
             <scope>runtime</scope>
         </dependency>
 
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/examples/java8/src/main/java/org/apache/camel/example/java8/MyApplication.java b/examples/java8/src/main/java/org/apache/camel/example/java8/MyApplication.java
index 86c9c4f..da1f64b 100644
--- a/examples/java8/src/main/java/org/apache/camel/example/java8/MyApplication.java
+++ b/examples/java8/src/main/java/org/apache/camel/example/java8/MyApplication.java
@@ -38,7 +38,7 @@ public final class MyApplication {
         main.run(args);
     }
 
-    private static class MyRouteBuilder extends RouteBuilder {
+    static class MyRouteBuilder extends RouteBuilder {
         @Override
         public void configure() throws Exception {
             from("timer:simple?period=503")
@@ -54,14 +54,11 @@ public final class MyApplication {
                         .body(Integer.class, b -> (b & 1) == 0)
                         .log("Received even number")
                     .when()
-                        .body(Integer.class, (b, h) -> h.containsKey("skip") ? false : (b & 1) == 0)
+                        .body(Integer.class, b -> (b & 1) != 0)
                         .log("Received odd number")
                     .when()
                         .body(Objects::isNull)
                         .log("Received null body")
-                    .when()
-                        .body(Integer.class, b -> (b & 1) != 0)
-                        .log("Received odd number")
                 .endChoice();
         }
 
diff --git a/examples/java8/src/test/java/org/apache/camel/example/java8/Java8Test.java b/examples/java8/src/test/java/org/apache/camel/example/java8/Java8Test.java
new file mode 100644
index 0000000..6913a72
--- /dev/null
+++ b/examples/java8/src/test/java/org/apache/camel/example/java8/Java8Test.java
@@ -0,0 +1,46 @@
+/*
+ * 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.example.java8;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A unit test checking that Camel supports properly lambda expressions and method references.
+ */
+class Java8Test extends CamelTestSupport {
+
+    @Test
+    void should_be_evaluated() {
+        NotifyBuilder notify = new NotifyBuilder(context).from("timer:*").whenCompleted(5).create();
+
+        assertTrue(
+            notify.matches(5, TimeUnit.SECONDS), "5 messages should be completed"
+        );
+    }
+
+    @Override
+    protected RoutesBuilder[] createRouteBuilders() {
+        return new RoutesBuilder[]{new MyApplication.MyRouteBuilder()};
+    }
+}
diff --git a/examples/jdbc/README.adoc b/examples/jdbc/README.adoc
index 6528c73..47a6dac 100644
--- a/examples/jdbc/README.adoc
+++ b/examples/jdbc/README.adoc
@@ -27,18 +27,18 @@ To stop the example hit ctrl+c
 
 === Configuration
 
-This example uses Spring to setup and configure the database, as well
+This example uses Spring to set up and configure the database, as well
 the CamelContext.
 
 You can see this in the following file:
 `+src/main/resources/META-INF/spring/camel-context.xml+`
 
-The spring config setups three routes as follow:
+The spring config setups three routes as follows:
 
-* `+sample-generator-route+` This route will generate sample data into database upon Camel starts.
-* `+query-update-route-part1/query-update-route-part2+` These two are connected together. It first query the database for NEW
-record to be process, invoke RecordProcess bean to do the work, then
-update the record as DONE so not to re-process on next polled.
+* `+sample-generator-route+` This route will generate sample data and load it into the database upon Camel starts.
+* `+query-update-route-part1/query-update-route-part2+` These two are connected together. It first queries the database for NEW
+records to be processed, invoke `RecordProcessor` bean to do the work, then
+update the record as DONE to not be processed again on the next polling.
 
 === Help and contributions
 
diff --git a/examples/jdbc/pom.xml b/examples/jdbc/pom.xml
index e0d213f..1e683ba 100644
--- a/examples/jdbc/pom.xml
+++ b/examples/jdbc/pom.xml
@@ -102,6 +102,12 @@
             <scope>runtime</scope>
         </dependency>
 
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-spring-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/examples/jdbc/src/test/java/org/apache/camel/example/jdbc/JdbcTest.java b/examples/jdbc/src/test/java/org/apache/camel/example/jdbc/JdbcTest.java
new file mode 100644
index 0000000..e6b6996
--- /dev/null
+++ b/examples/jdbc/src/test/java/org/apache/camel/example/jdbc/JdbcTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.example.jdbc;
+
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.model.ModelCamelContext;
+import org.apache.camel.test.spring.junit5.CamelSpringTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A unit test allowing to check that the CRUD operations can be executed thanks to the Camel jdbc component.
+ */
+@CamelSpringTest
+@ContextConfiguration("/META-INF/spring/camel-context.xml")
+class JdbcTest {
+
+    @Autowired
+    ModelCamelContext context;
+
+    @Test
+    void should_execute_crud_operations() {
+        NotifyBuilder notify = new NotifyBuilder(context).whenCompleted(10).wereSentTo("log:updateDone").create();
+
+        assertTrue(
+            notify.matches(30, TimeUnit.SECONDS), "10 messages should be completed"
+        );
+    }
+}
diff --git a/examples/jms-file/README.adoc b/examples/jms-file/README.adoc
index 739505a..d2dda24 100644
--- a/examples/jms-file/README.adoc
+++ b/examples/jms-file/README.adoc
@@ -22,11 +22,11 @@ The example should run if you type
 
 [source,sh]
 ----
-$ mvn exec:java -PExample
+$ mvn exec:java
 ----
 
 After the example is complete, then there should be 10 files written in
-the test directory.
+the directory `target/messages`.
 
 === Help and contributions
 
diff --git a/examples/jms-file/pom.xml b/examples/jms-file/pom.xml
index 29c2e34..4ea5f07 100644
--- a/examples/jms-file/pom.xml
+++ b/examples/jms-file/pom.xml
@@ -122,18 +122,14 @@
             <scope>runtime</scope>
             <version>${log4j2-version}</version>
         </dependency>
-
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
-    <profiles>
-        <profile>
-            <id>Example</id>
-            <properties>
-                <target.main.class>org.apache.camel.example.jmstofile.CamelJmsToFileExample</target.main.class>
-            </properties>
-        </profile>
-    </profiles>
-
     <build>
         <plugins>
             <!-- Allows the example to be run via 'mvn compile exec:java' -->
@@ -141,7 +137,7 @@
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>exec-maven-plugin</artifactId>
                 <configuration>
-                    <mainClass>${target.main.class}</mainClass>
+                    <mainClass>org.apache.camel.example.jmstofile.CamelJmsToFileExample</mainClass>
                     <includePluginDependencies>false</includePluginDependencies>
                 </configuration>
             </plugin>
diff --git a/examples/jms-file/src/main/java/org/apache/camel/example/jmstofile/CamelJmsToFileExample.java b/examples/jms-file/src/main/java/org/apache/camel/example/jmstofile/CamelJmsToFileExample.java
index 5905810..7f8a66d 100644
--- a/examples/jms-file/src/main/java/org/apache/camel/example/jmstofile/CamelJmsToFileExample.java
+++ b/examples/jms-file/src/main/java/org/apache/camel/example/jmstofile/CamelJmsToFileExample.java
@@ -26,7 +26,7 @@ import org.apache.camel.impl.DefaultCamelContext;
 import static java.util.Collections.singletonList;
 
 /**
- * An example class for demonstrating some of the basics behind Camel. This
+ * An example class for demonstrating some basics behind Camel. This
  * example sends some text messages on to a JMS Queue, consumes them and
  * persists them to disk
  */
@@ -41,28 +41,20 @@ public final class CamelJmsToFileExample {
             // end::e1[]
             // Set up the ActiveMQ JMS Components
             // tag::e2[]
-            ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false");
-            connectionFactory.setTrustAllPackages(false);
-            connectionFactory.setTrustedPackages(singletonList("org.apache.camel.example.jmstofile"));
-
-
-            // Note we can explicit name the component
+            ActiveMQConnectionFactory connectionFactory = createActiveMQConnectionFactory();
+            // Note we can explicitly name the component
             context.addComponent("test-jms", JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));
             // end::e2[]
             // Add some configuration by hand ...
             // tag::e3[]
-            context.addRoutes(new RouteBuilder() {
-                public void configure() {
-                    from("test-jms:queue:test.queue").to("file://test");
-                }
-            });
+            context.addRoutes(new MyRouteBuilder());
             // end::e3[]
+            // Now everything is set up - lets start the context
+            context.start();
             // Camel template - a handy class for kicking off exchanges
             // tag::e4[]
             try (ProducerTemplate template = context.createProducerTemplate()) {
                 // end::e4[]
-                // Now everything is set up - lets start the context
-                context.start();
                 // Now send some test text to a component - for this case a JMS Queue
                 // The text get converted to JMS messages - and sent to the Queue
                 // test.queue
@@ -81,7 +73,21 @@ public final class CamelJmsToFileExample {
 
             // wait a bit and then stop
             Thread.sleep(1000);
-            context.stop();
+        }
+    }
+
+    static ActiveMQConnectionFactory createActiveMQConnectionFactory() {
+        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false");
+        connectionFactory.setTrustAllPackages(false);
+        connectionFactory.setTrustedPackages(singletonList("org.apache.camel.example.jmstofile"));
+        return connectionFactory;
+    }
+
+    static class MyRouteBuilder extends RouteBuilder {
+
+        @Override
+        public void configure() {
+            from("test-jms:queue:test.queue").to("file:target/messages");
         }
     }
 }
diff --git a/examples/jms-file/src/test/java/org/apache/camel/example/jmstofile/JmsToFileTest.java b/examples/jms-file/src/test/java/org/apache/camel/example/jmstofile/JmsToFileTest.java
new file mode 100644
index 0000000..9cb808f
--- /dev/null
+++ b/examples/jms-file/src/test/java/org/apache/camel/example/jmstofile/JmsToFileTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.example.jmstofile;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.camel.CamelContext;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.component.jms.JmsComponent;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.camel.example.jmstofile.CamelJmsToFileExample.createActiveMQConnectionFactory;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A unit test checking that Camel can read messages from a JMS queue and store them into the file system as files.
+ */
+class JmsToFileTest extends CamelTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext camelContext = super.createCamelContext();
+        // Set up the ActiveMQ JMS Components
+        // tag::e2[]
+        ActiveMQConnectionFactory connectionFactory = createActiveMQConnectionFactory();
+        // Note we can explicitly name the component
+        camelContext.addComponent("test-jms", JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));
+
+        return camelContext;
+    }
+
+    @Test
+    void should_store_jms_messages_as_files() throws IOException {
+        Path targetDir = Paths.get("target/messages");
+        long before = Files.exists(targetDir) ? Files.list(targetDir).count() : 0L;
+        NotifyBuilder notify = new NotifyBuilder(context).from("test-jms:*").whenCompleted(5).create();
+        for (int i = 0; i < 5; i++) {
+            template.sendBody("test-jms:queue:test.queue", "Test Message: " + i);
+        }
+        assertTrue(
+            notify.matches(5, TimeUnit.SECONDS), "5 messages should be completed"
+        );
+        long after = Files.list(targetDir).count();
+        assertEquals(5L, after - before, "5 files should have been created");
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new CamelJmsToFileExample.MyRouteBuilder();
+    }
+}
diff --git a/examples/jmx/pom.xml b/examples/jmx/pom.xml
index 8c58f33..3c5f6a7 100644
--- a/examples/jmx/pom.xml
+++ b/examples/jmx/pom.xml
@@ -99,6 +99,12 @@
             <scope>test</scope>
         </dependency>
 
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-spring-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/examples/jmx/src/test/java/org/apache/camel/example/jmx/JMXTest.java b/examples/jmx/src/test/java/org/apache/camel/example/jmx/JMXTest.java
new file mode 100644
index 0000000..49f7d92
--- /dev/null
+++ b/examples/jmx/src/test/java/org/apache/camel/example/jmx/JMXTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.example.jmx;
+
+import org.apache.camel.builder.AdviceWith;
+import org.apache.camel.builder.AdviceWithRouteBuilder;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.model.ModelCamelContext;
+import org.apache.camel.test.spring.junit5.CamelSpringTest;
+import org.apache.camel.test.spring.junit5.UseAdviceWith;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A unit test allowing to check that Camel can subscribe to the notifications of a custom MBean.
+ */
+@CamelSpringTest
+@ContextConfiguration("/META-INF/spring/camel-context.xml")
+@UseAdviceWith
+class JMXTest {
+
+    @Autowired
+    ModelCamelContext context;
+
+    @Test
+    void should_receive_mbean_notifications() throws Exception {
+        // Replace the from endpoint to change the value of the option period
+        AdviceWith.adviceWith(context.getRouteDefinitions().get(1), context, new AdviceWithRouteBuilder() {
+            @Override
+            public void configure() {
+                replaceFromWith("timer:foo?period=1000");
+            }
+        });
+
+        // must start Camel after we are done using advice-with
+        context.start();
+
+        NotifyBuilder notify = new NotifyBuilder(context).whenCompleted(3).wereSentTo("log:jmxEvent").create();
+
+        assertTrue(
+            notify.matches(10, TimeUnit.SECONDS), "3 messages should be completed"
+        );
+    }
+}
diff --git a/examples/jooq/README.adoc b/examples/jooq/README.adoc
index 45fa579..d8fd537 100644
--- a/examples/jooq/README.adoc
+++ b/examples/jooq/README.adoc
@@ -4,7 +4,7 @@
 
 This example shows how to use JOOQ library with Camel to build type safe SQL queries through its API.
 
-Example project contains SQL script to create database.
+Example project contains an SQL script to create database.
 Database is generated every time when Maven `generate-sources` phase is triggered.
 JOOQ classes are generated inside `target/generated-sources/jooq` directory by `jooq-codegen-maven` plugin.
 
@@ -13,7 +13,7 @@ You will need to compile this example first:
 
 [source,sh]
 ----
-$ mvn clean install
+$ mvn compile
 ----
 
 This command will generate the database and JOOQ classes.
diff --git a/examples/jooq/pom.xml b/examples/jooq/pom.xml
index f33701d..9c435eb 100644
--- a/examples/jooq/pom.xml
+++ b/examples/jooq/pom.xml
@@ -100,6 +100,13 @@
             <artifactId>hsqldb</artifactId>
             <version>${hsqldb-version}</version>
         </dependency>
+
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-spring-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/examples/jooq/src/test/java/org/apache/camel/examples/jooq/JOOQTest.java b/examples/jooq/src/test/java/org/apache/camel/examples/jooq/JOOQTest.java
new file mode 100644
index 0000000..d8223e4
--- /dev/null
+++ b/examples/jooq/src/test/java/org/apache/camel/examples/jooq/JOOQTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.examples.jooq;
+
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.model.ModelCamelContext;
+import org.apache.camel.test.spring.junit5.CamelSpringTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A unit test allowing to check that Camel can rely on jOOQ to execute CRUD operations.
+ */
+@CamelSpringTest
+@ContextConfiguration("/META-INF/spring/camel-context.xml")
+class JOOQTest {
+
+    @Autowired
+    ModelCamelContext context;
+
+    @Test
+    void should_execute_crud_operations() {
+        NotifyBuilder notify = new NotifyBuilder(context).fromRoute("produce-route").whenCompleted(3)
+                .and().fromRoute("consume-route").whenCompleted(3).create();
+
+        assertTrue(
+            notify.matches(10, TimeUnit.SECONDS), "3 messages should be completed"
+        );
+    }
+}
diff --git a/examples/kafka/README.adoc b/examples/kafka/README.adoc
index 8f7df06..2239366 100644
--- a/examples/kafka/README.adoc
+++ b/examples/kafka/README.adoc
@@ -12,22 +12,43 @@ This project consists of the following examples:
 
 === Preparing Kafka
 
-This example requires that Kafka Server is up and running.
+This example requires that Kafka Server is up and running. For this, assuming that environment variable `KAFKA` has been
+set to the home directory of the Kafka distribution, you will need to start ZooKeeper and a Kafka Broker thanks to
+the next commands:
 
-    $ ${KAFKA}/bin/zookeeper-server-start.sh ${KAFKA}/config/zookeeper.properties
-    $ ${KAFKA}/bin/kafka-server-start.sh ${KAFKA}/config/server.properties
+On Windows run
+
+[source,sh]
+----
+$ %KAFKA%\bin\windows\zookeeper-server-start.bat ${KAFKA}/config/zookeeper.properties
+$ %KAFKA%\bin\windows\kafka-server-start.bat ${KAFKA}/config/server.properties
+----
+
+On linux run
+
+[source,sh]
+----
+$ ${KAFKA}/bin/zookeeper-server-start.sh ${KAFKA}/config/zookeeper.properties
+$ ${KAFKA}/bin/kafka-server-start.sh ${KAFKA}/config/server.properties
+----
 
 You will need to create following topics before you run the examples.
 
-On windows run
+On Windows run
 
-    $ ${KAFKA}/bin/kafka-topics.bat --create --zookeeper <zookeeper host ip>:<port> --replication-factor 1 --partitions 2 --topic TestLog
-    $ ${KAFKA}/bin/kafka-topics.bat --create --zookeeper <zookeeper host ip>:<port> --replication-factor 1 --partitions 1 --topic AccessLog
+[source,sh]
+----
+$ %KAFKA%\bin\windows\kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 2 --topic TestLog
+$ %KAFKA%\bin\windows\kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic AccessLog
+----
 
 On linux run
-    
-    $ ${KAFKA}/bin/kafka-topics.sh --create --zookeeper <zookeeper host ip>:<port> --replication-factor 1 --partitions 2 --topic TestLog
-    $ ${KAFKA}/bin/kafka-topics.sh --create --zookeeper <zookeeper host ip>:<port> --replication-factor 1 --partitions 1 --topic AccessLog
+
+[source,sh]
+----
+$ ${KAFKA}/bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 2 --topic TestLog
+$ ${KAFKA}/bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic AccessLog
+----
 
 === Build
 
@@ -40,18 +61,18 @@ $ mvn compile
 
 === Run
 
-Run the consumer first in separate shell 
+Run the consumer first in a shell
 
 [source,sh]
 ----
-$ mvn compile exec:java -Pkafka-consumer
+$ mvn exec:java -Pkafka-consumer
 ----
 
-Run the message producer in the seperate shell
+Run the message producer in a separate shell
 
 [source,sh]
 ----
-$ mvn compile exec:java -Pkafka-producer
+$ mvn exec:java -Pkafka-producer
 ----
 
 Initially, some messages are sent programmatically. 
diff --git a/examples/kafka/pom.xml b/examples/kafka/pom.xml
index 28e89d1..a2ac26c 100644
--- a/examples/kafka/pom.xml
+++ b/examples/kafka/pom.xml
@@ -84,6 +84,18 @@
             <artifactId>log4j-slf4j-impl</artifactId>
             <version>${log4j2-version}</version>
         </dependency>
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>kafka</artifactId>
+            <version>${testcontainers-version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
diff --git a/examples/kafka/src/main/java/org/apache/camel/example/kafka/MessageConsumerClient.java b/examples/kafka/src/main/java/org/apache/camel/example/kafka/MessageConsumerClient.java
index 874e27d..f5ca39b 100644
--- a/examples/kafka/src/main/java/org/apache/camel/example/kafka/MessageConsumerClient.java
+++ b/examples/kafka/src/main/java/org/apache/camel/example/kafka/MessageConsumerClient.java
@@ -18,11 +18,12 @@ package org.apache.camel.example.kafka;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.builder.RouteBuilder;
-import org.apache.camel.builder.component.ComponentsBuilderFactory;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.camel.example.kafka.MessagePublisherClient.setUpKafkaComponent;
+
 public final class MessageConsumerClient {
 
     private static final Logger LOG = LoggerFactory.getLogger(MessageConsumerClient.class);
@@ -36,35 +37,32 @@ public final class MessageConsumerClient {
 
         try (CamelContext camelContext = new DefaultCamelContext()) {
 
+            LOG.info("About to start route: Kafka Server -> Log ");
+            // Set the location of the configuration
+            camelContext.getPropertiesComponent().setLocation("classpath:application.properties");
+            // Set up the Kafka component
+            setUpKafkaComponent(camelContext);
             // Add route to send messages to Kafka
 
-            camelContext.addRoutes(new RouteBuilder() {
-                public void configure() {
-                    camelContext.getPropertiesComponent().setLocation("classpath:application.properties");
-
-                    log.info("About to start route: Kafka Server -> Log ");
-
-                    // setup kafka component with the brokers
-                    ComponentsBuilderFactory.kafka()
-                            .brokers("{{kafka.host}}:{{kafka.port}}")
-                            .register(camelContext, "kafka");
-
-                    from("kafka:{{consumer.topic}}"
-                            + "?maxPollRecords={{consumer.maxPollRecords}}"
-                            + "&consumersCount={{consumer.consumersCount}}"
-                            + "&seekTo={{consumer.seekTo}}"
-                            + "&groupId={{consumer.group}}")
-                            .routeId("FromKafka")
-                            .log("${body}");
-                }
-            });
+            camelContext.addRoutes(createRouteBuilder());
             camelContext.start();
 
             // let it run for 5 minutes before shutting down
             Thread.sleep(5L * 60 * 1000);
-
-            camelContext.stop();
         }
     }
 
+    static RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+            from("kafka:{{consumer.topic}}"
+                    + "?maxPollRecords={{consumer.maxPollRecords}}"
+                    + "&consumersCount={{consumer.consumersCount}}"
+                    + "&seekTo={{consumer.seekTo}}"
+                    + "&groupId={{consumer.group}}")
+                    .routeId("FromKafka")
+                    .log("${body}");
+            }
+        };
+    }
 }
diff --git a/examples/kafka/src/main/java/org/apache/camel/example/kafka/MessagePublisherClient.java b/examples/kafka/src/main/java/org/apache/camel/example/kafka/MessagePublisherClient.java
index 1787459..00f9fc1 100644
--- a/examples/kafka/src/main/java/org/apache/camel/example/kafka/MessagePublisherClient.java
+++ b/examples/kafka/src/main/java/org/apache/camel/example/kafka/MessagePublisherClient.java
@@ -45,42 +45,12 @@ public final class MessagePublisherClient {
         String testKafkaMessage = "Test Message from  MessagePublisherClient " + Calendar.getInstance().getTime();
 
         try (CamelContext camelContext = new DefaultCamelContext()) {
-
+            // Set the location of the configuration
+            camelContext.getPropertiesComponent().setLocation("classpath:application.properties");
+            // Set up the Kafka component
+            setUpKafkaComponent(camelContext);
             // Add route to send messages to Kafka
-
-            camelContext.addRoutes(new RouteBuilder() {
-                public void configure() {
-                    camelContext.getPropertiesComponent().setLocation("classpath:application.properties");
-
-                    // setup kafka component with the brokers
-                    ComponentsBuilderFactory.kafka()
-                            .brokers("{{kafka.host}}:{{kafka.port}}")
-                            .register(camelContext, "kafka");
-
-                    from(DIRECT_KAFKA_START).routeId("DirectToKafka")
-                            .to("kafka:{{producer.topic}}").log(HEADERS);
-
-                    // Topic can be set in header as well.
-
-                    from("direct:kafkaStartNoTopic").routeId("kafkaStartNoTopic")
-                            .to("kafka:dummy")
-                            .log(HEADERS);
-
-                    // Use custom partitioner based on the key.
-
-                    from(DIRECT_KAFKA_START_WITH_PARTITIONER).routeId("kafkaStartWithPartitioner")
-                            .to("kafka:{{producer.topic}}?partitioner={{producer.partitioner}}")
-                            .log(HEADERS);
-
-
-                    // Takes input from the command line.
-
-                    from("stream:in").setHeader(KafkaConstants.PARTITION_KEY, simple("0"))
-                            .setHeader(KafkaConstants.KEY, simple("1")).to(DIRECT_KAFKA_START);
-
-                }
-
-            });
+            camelContext.addRoutes(createRouteBuilder());
 
             try (ProducerTemplate producerTemplate = camelContext.createProducerTemplate()) {
                 camelContext.start();
@@ -115,9 +85,42 @@ public final class MessagePublisherClient {
             System.out.println("Enter text on the line below : [Press Ctrl-C to exit.] ");
 
             Thread.sleep(5L * 60 * 1000);
-
-            camelContext.stop();
         }
     }
 
+    static RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+            from(DIRECT_KAFKA_START).routeId("DirectToKafka")
+                    .to("kafka:{{producer.topic}}").log(HEADERS);
+
+            // Topic can be set in header as well.
+
+            from("direct:kafkaStartNoTopic").routeId("kafkaStartNoTopic")
+                    .to("kafka:dummy")
+                    .log(HEADERS);
+
+            // Use custom partitioner based on the key.
+
+            from(DIRECT_KAFKA_START_WITH_PARTITIONER).routeId("kafkaStartWithPartitioner")
+                    .to("kafka:{{producer.topic}}?partitioner={{producer.partitioner}}")
+                    .log(HEADERS);
+
+
+            // Takes input from the command line.
+
+            from("stream:in").id("input").setHeader(KafkaConstants.PARTITION_KEY, simple("0"))
+                    .setHeader(KafkaConstants.KEY, simple("1")).to(DIRECT_KAFKA_START);
+
+            }
+        };
+    }
+
+    static void setUpKafkaComponent(CamelContext camelContext) {
+        // setup kafka component with the brokers
+        ComponentsBuilderFactory.kafka()
+                .brokers("{{kafka.host}}:{{kafka.port}}")
+                .register(camelContext, "kafka");
+    }
+
 }
diff --git a/examples/kafka/src/main/resources/application.properties b/examples/kafka/src/main/resources/application.properties
index 531ad65..ab11ae6 100644
--- a/examples/kafka/src/main/resources/application.properties
+++ b/examples/kafka/src/main/resources/application.properties
@@ -26,7 +26,7 @@ producer.partitioner=org.apache.camel.example.kafka.StringPartitioner
 
 # Consumer properties 
 
-# One consumer can listen to more than one topics.[ TestLog,AccessLog ] 
+# One consumer can listen to more than one topic.[ TestLog,AccessLog ]
 consumer.topic=TestLog
 consumer.group=kafkaGroup
 consumer.maxPollRecords=5000
diff --git a/examples/kafka/src/test/java/org/apache/camel/example/kafka/KafkaTest.java b/examples/kafka/src/test/java/org/apache/camel/example/kafka/KafkaTest.java
new file mode 100644
index 0000000..074fbf0
--- /dev/null
+++ b/examples/kafka/src/test/java/org/apache/camel/example/kafka/KafkaTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.example.kafka;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.AdviceWith;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.KafkaContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.camel.example.kafka.MessagePublisherClient.setUpKafkaComponent;
+import static org.apache.camel.util.PropertiesHelper.asProperties;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A unit test checking that Camel can produce and consume messages to / from a Kafka broker.
+ */
+class KafkaTest extends CamelTestSupport {
+
+    private static final String IMAGE = "confluentinc/cp-kafka:6.2.2";
+    private static KafkaContainer CONTAINER;
+
+    @BeforeAll
+    static void init() {
+        CONTAINER = new KafkaContainer(DockerImageName.parse(IMAGE));
+        CONTAINER.start();
+    }
+
+    @AfterAll
+    static void destroy() {
+        if (CONTAINER != null) {
+            CONTAINER.stop();
+        }
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext camelContext = super.createCamelContext();
+        // Set the location of the configuration
+        camelContext.getPropertiesComponent().setLocation("classpath:application.properties");
+        // Override the host and port of the broker
+        camelContext.getPropertiesComponent().setOverrideProperties(
+            asProperties(
+                "kafka.host", CONTAINER.getHost(),
+                "kafka.port", Integer.toString(CONTAINER.getMappedPort(9093))
+            )
+        );
+        setUpKafkaComponent(camelContext);
+        return camelContext;
+    }
+
+    @Test
+    void should_exchange_messages_with_a_kafka_broker() throws Exception {
+        // Replace the from endpoint to send messages easily
+        AdviceWith.adviceWith(context, "input", ad -> ad.replaceFromWith("direct:in"));
+
+        // must start Camel after we are done using advice-with
+        context.start();
+
+        String message = UUID.randomUUID().toString();
+        template.sendBody("direct:in", message);
+        NotifyBuilder notify = new NotifyBuilder(context).fromRoute("FromKafka")
+                .whenCompleted(1).whenBodiesReceived(message).create();
+        assertTrue(
+            notify.matches(20, TimeUnit.SECONDS), "1 message should be completed"
+        );
+    }
+
+    @Override
+    protected RoutesBuilder[] createRouteBuilders() {
+        return new RoutesBuilder[]{
+            MessageConsumerClient.createRouteBuilder(), MessagePublisherClient.createRouteBuilder()
+        };
+    }
+}
diff --git a/examples/kamelet/pom.xml b/examples/kamelet/pom.xml
index 03234fa..4129c25 100644
--- a/examples/kamelet/pom.xml
+++ b/examples/kamelet/pom.xml
@@ -81,7 +81,12 @@
             <artifactId>logback-classic</artifactId>
             <version>${logback-version}</version>
         </dependency>
-
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/examples/kamelet/src/test/java/org/apache/camel/example/KameletTest.java b/examples/kamelet/src/test/java/org/apache/camel/example/KameletTest.java
new file mode 100644
index 0000000..ed7c19c
--- /dev/null
+++ b/examples/kamelet/src/test/java/org/apache/camel/example/KameletTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.example;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A unit test checking that Camel can build routes thanks to Kamelet.
+ */
+class KameletTest extends CamelTestSupport {
+
+    @Test
+    void should_build_routes_from_kamelets() {
+        NotifyBuilder notify = new NotifyBuilder(context).whenCompleted(3).wereSentTo("log:myKamelet1").and()
+            .whenCompleted(3).wereSentTo("log:myKamelet2").create();
+        assertTrue(
+            notify.matches(20, TimeUnit.SECONDS), "3 messages should be completed"
+        );
+    }
+
+    @Override
+    protected RoutesBuilder[] createRouteBuilders() {
+        return new RoutesBuilder[]{new MyRouteTemplates(), new MyRoutes()};
+    }
+}
diff --git a/examples/main-artemis/README.adoc b/examples/main-artemis/README.adoc
index 48b242b..b0b9472 100644
--- a/examples/main-artemis/README.adoc
+++ b/examples/main-artemis/README.adoc
@@ -15,9 +15,12 @@ First install https://activemq.apache.org/components/artemis/[Apache ActiveMQ Ar
 and create a broker, such as `mybroker`, and create the admin user as `admin` as username
 and `admin` as password:
 
-    bin/artemis create mybroker
-    cd mybroker
-    bin/artemis run
+[source,sh]
+----
+$ bin/artemis create mybroker
+$ cd mybroker
+$ bin/artemis run
+----
 
 Then you can run this example using
 
diff --git a/examples/main-artemis/src/main/java/org/apache/camel/example/MyConfiguration.java b/examples/main-artemis/src/main/java/org/apache/camel/example/MyConfiguration.java
index be6094f..92feb42 100644
--- a/examples/main-artemis/src/main/java/org/apache/camel/example/MyConfiguration.java
+++ b/examples/main-artemis/src/main/java/org/apache/camel/example/MyConfiguration.java
@@ -23,7 +23,7 @@ public class MyConfiguration {
 
     /**
      * Creates the Artemis JMS ConnectionFactory and bind it to the Camel registry
-     * so we can do autowiring on the Camel JMS component.
+     * so we can do auto-wiring on the Camel JMS component.
      * See more details in the application.properties file.
      */
 //    @BindToRegistry
diff --git a/examples/main-artemis/src/main/resources/application.properties b/examples/main-artemis/src/main/resources/application.properties
index 9da3393..ac4e21c 100644
--- a/examples/main-artemis/src/main/resources/application.properties
+++ b/examples/main-artemis/src/main/resources/application.properties
@@ -38,7 +38,7 @@ camel.component.jms.connection-factory=#class:org.apache.activemq.artemis.jms.cl
 ### camel.component.jms.connection-factory.target-connection-factory.password=admin
 ### camel.component.jms.connection-factory.target-connection-factory.brokerURL=tcp://localhost:61616
 
-# this is used if you enable the @BindToRegistry in MyConfiguration class to autowire
+# this is used if you enable the @BindToRegistry in MyConfiguration class to auto-wire
 # the Artemis JMS ConnectionFactory via Java source code
 # the url to the Artemis Broker
 ### artemisBroker=tcp://localhost:61616
diff --git a/examples/mongodb/README.adoc b/examples/mongodb/README.adoc
index fd49b40..233fb05 100644
--- a/examples/mongodb/README.adoc
+++ b/examples/mongodb/README.adoc
@@ -2,13 +2,24 @@
 
 This example shows how to use Camel MongoDB component. There are three REST endpoints that will trigger the MongoDB component for reading and for writing.
 
-=== Run a Mongo instance
+=== Run a MongoDB instance
 
-You need to have an instance of MongoDB server running locally. You can find a docker-compose file under `/docker/` directory.
+You need to have an instance of MongoDB server running locally.
 
+You can run it as a Docker container:
+
+[source,sh]
+----
+docker run -d --name mongodb -p 27017:27017 mongo
+----
+
+=== Build
+
+You will need to compile this example first:
+
+[source,sh]
 ----
-$ cd docker
-$ docker-compose up
+$ mvn compile
 ----
 
 === Run the Camel integration
@@ -27,16 +38,23 @@ You can insert an "hello" document by POSTing to `/hello` endpoint:
 $ curl -X POST -H "Content-Type: application/json" -d '{"text":"Hello from Camel"}' http://localhost:8081/hello
 ----
 
+The result of the query is of type:
+
+----
+$ Document{{text=Hello from Camel, _id=<document-id>}}
+----
+
+
 You can read all the documents by requesting to `/` endpoint:
 
 ----
 $ curl localhost:8081
 ----
 
-You can also read a single document by providing the `id` parameter:
+You can also read a single document by setting the `id` query parameter to the target `<document-id>`:
 
 ----
-$ curl localhost:8081/hello?id=5eaa94933aff184354c4a874
+$ curl "localhost:8081/hello?id=<document-id>"
 ----
 
 === Help and contributions
diff --git a/examples/mongodb/docker/docker-compose.yml b/examples/mongodb/docker/docker-compose.yml
deleted file mode 100644
index aa5b5f8..0000000
--- a/examples/mongodb/docker/docker-compose.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-version: '3.1'
-
-services:
-
-  mongo:
-    image: mongo
-    ports:
-      - 27017:27017
\ No newline at end of file
diff --git a/examples/mongodb/pom.xml b/examples/mongodb/pom.xml
index 81c3b47..acc87c7 100644
--- a/examples/mongodb/pom.xml
+++ b/examples/mongodb/pom.xml
@@ -76,6 +76,24 @@
             <artifactId>slf4j-simple</artifactId>
             <version>${slf4j-version}</version>
         </dependency>
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>mongodb</artifactId>
+            <version>${testcontainers-version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.rest-assured</groupId>
+            <artifactId>rest-assured</artifactId>
+            <version>${rest-assured-version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/examples/mongodb/src/main/java/org/apache/camel/example/mongodb/MongoDBFindAllRouteBuilder.java b/examples/mongodb/src/main/java/org/apache/camel/example/mongodb/MongoDBFindAllRouteBuilder.java
index f6dda97..b601940 100644
--- a/examples/mongodb/src/main/java/org/apache/camel/example/mongodb/MongoDBFindAllRouteBuilder.java
+++ b/examples/mongodb/src/main/java/org/apache/camel/example/mongodb/MongoDBFindAllRouteBuilder.java
@@ -16,10 +16,7 @@
  */
 package org.apache.camel.example.mongodb;
 
-import com.mongodb.client.model.Filters;
 import org.apache.camel.builder.RouteBuilder;
-import org.apache.camel.component.mongodb.MongoDbConstants;
-import org.bson.types.ObjectId;
 
 public class MongoDBFindAllRouteBuilder extends RouteBuilder {
 
diff --git a/examples/mongodb/src/main/java/org/apache/camel/example/mongodb/MongoDBInsertRouteBuilder.java b/examples/mongodb/src/main/java/org/apache/camel/example/mongodb/MongoDBInsertRouteBuilder.java
index 596cb14..227e858 100644
--- a/examples/mongodb/src/main/java/org/apache/camel/example/mongodb/MongoDBInsertRouteBuilder.java
+++ b/examples/mongodb/src/main/java/org/apache/camel/example/mongodb/MongoDBInsertRouteBuilder.java
@@ -17,7 +17,6 @@
 package org.apache.camel.example.mongodb;
 
 import org.apache.camel.builder.RouteBuilder;
-import org.bson.types.ObjectId;
 
 public class MongoDBInsertRouteBuilder extends RouteBuilder {
 
diff --git a/examples/mongodb/src/test/java/org/apache/camel/example/mongodb/MongoDBTest.java b/examples/mongodb/src/test/java/org/apache/camel/example/mongodb/MongoDBTest.java
new file mode 100644
index 0000000..c3562a7
--- /dev/null
+++ b/examples/mongodb/src/test/java/org/apache/camel/example/mongodb/MongoDBTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.example.mongodb;
+
+import com.mongodb.client.MongoClients;
+import io.restassured.response.Response;
+import org.apache.camel.CamelContext;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.MongoDBContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.matchesPattern;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A unit test checking that Camel can execute CRUD operations against MongoDB.
+ */
+class MongoDBTest extends CamelTestSupport {
+
+    private static final String IMAGE = "mongo:5.0";
+    private static MongoDBContainer CONTAINER;
+
+    private static final String BASE_URI = "http://localhost:8081";
+
+    @BeforeAll
+    static void init() {
+        CONTAINER = new MongoDBContainer(DockerImageName.parse(IMAGE));
+        CONTAINER.start();
+    }
+
+    @AfterAll
+    static void destroy() {
+        if (CONTAINER != null) {
+            CONTAINER.stop();
+        }
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext camelContext = super.createCamelContext();
+        // Bind the MongoDB client with the host and port of the container
+        camelContext.getRegistry().bind(
+            "myDb",
+            MongoClients.create(String.format("mongodb://%s:%d", CONTAINER.getHost(), CONTAINER.getMappedPort(27017)))
+        );
+        return camelContext;
+    }
+
+    @Test
+    void should_execute_crud_operations() throws Exception {
+        // Insert a Document
+        Response response = given()
+            .baseUri(BASE_URI)
+        .when()
+            .contentType("application/json")
+            .body("{\"text\":\"Hello from Camel\"}")
+            .post("/hello")
+        .then()
+            .body(matchesPattern("Document\\{\\{text=Hello from Camel, _id=(.*)}}"))
+        .extract()
+            .response();
+        Matcher matcher = Pattern.compile(".*_id=(.*)}}.*").matcher(response.asString());
+        assertTrue(matcher.find(), "The response should match the regular expression");
+        String id = matcher.group(1);
+        // Find By Id
+        given()
+            .baseUri(BASE_URI)
+        .when()
+            .queryParam("id", id)
+            .get("/hello")
+        .then()
+            .body(containsString(String.format("_id=%s", id)));
+        // Find All
+        given()
+            .baseUri(BASE_URI)
+        .when()
+            .get("/")
+        .then()
+            .body(containsString(String.format("_id=%s", id)));
+    }
+
+    @Override
+    protected RoutesBuilder[] createRouteBuilders() {
+        return new RoutesBuilder[]{
+            new MongoDBFindByIDRouteBuilder(), new MongoDBFindAllRouteBuilder(), new MongoDBInsertRouteBuilder()
+        };
+    }
+}
diff --git a/examples/netty-custom-correlation/README.adoc b/examples/netty-custom-correlation/README.adoc
index 152c823..238d134 100644
--- a/examples/netty-custom-correlation/README.adoc
+++ b/examples/netty-custom-correlation/README.adoc
@@ -8,6 +8,15 @@ to multiplex concurrent messages over the same connection. A custom correlation
 is implemented to be able to correlate the request and response message pairs so you
 do not mix-data to wrong replies.
 
+=== Build
+
+You will need to compile this example first:
+
+[source,sh]
+----
+$ mvn compile
+----
+
 === How to run
 
 You can run this example using two JVMs.
@@ -15,19 +24,19 @@ You can run this example using two JVMs.
 To start the server run:
 
 ----
-$ mvn compile exec:java -P server
+$ mvn exec:java -P server
 ----
 
 To start the client run:
 
 ----
-$ mvn compile exec:java -P client
+$ mvn exec:java -P client
 ----
 
 In the client output you should see it logs request/response pairs.
 For requests that contains the word `beer` is delayed on the server side, and you
-should notice that its corresponding reply is correlated correclty to its beloing request thread.
-Also the messages can be inter-leaved when some messages are faster than others.
+should notice that its corresponding reply is correlated correctly to its belonging request thread.
+Also, the messages can be inter-leaved when some messages are faster than others.
 
 === Help and contributions
 
diff --git a/examples/netty-custom-correlation/pom.xml b/examples/netty-custom-correlation/pom.xml
index ea5ce92..00bea2a 100644
--- a/examples/netty-custom-correlation/pom.xml
+++ b/examples/netty-custom-correlation/pom.xml
@@ -85,7 +85,12 @@
             <artifactId>log4j-jul</artifactId>
             <version>${log4j2-version}</version>
         </dependency>
-
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
diff --git a/examples/netty-custom-correlation/src/main/java/org/apache/camel/example/netty/MyClient.java b/examples/netty-custom-correlation/src/main/java/org/apache/camel/example/netty/MyClient.java
index 815c5a7..d4a11a5 100644
--- a/examples/netty-custom-correlation/src/main/java/org/apache/camel/example/netty/MyClient.java
+++ b/examples/netty-custom-correlation/src/main/java/org/apache/camel/example/netty/MyClient.java
@@ -35,31 +35,36 @@ public final class MyClient {
         Main main = new Main();
         main.configure().addRoutesBuilder(new MyRouteBuilder());
 
+        MyCorrelationManager manager = createCorrelationManager();
+
+        main.bind("myEncoder", new MyCodecEncoderFactory());
+        main.bind("myDecoder", new MyCodecDecoderFactory());
+        main.bind("myManager", manager);
+        main.run(args);
+    }
+
+    static MyCorrelationManager createCorrelationManager() {
         // setup correlation manager and its timeout (when a request has not received a response within the given time millis)
         MyCorrelationManager manager = new MyCorrelationManager();
         // set timeout for each request message that did not receive a reply message
         manager.setTimeout(5000);
         // set the logging level when a timeout was hit, ny default its DEBUG
         manager.setTimeoutLoggingLevel(LoggingLevel.INFO);
-
-        main.bind("myEncoder", new MyCodecEncoderFactory());
-        main.bind("myDecoder", new MyCodecDecoderFactory());
-        main.bind("myManager", manager);
-        main.run(args);
+        return manager;
     }
 
-    public static class MyRouteBuilder extends RouteBuilder {
+    static class MyRouteBuilder extends RouteBuilder {
 
-        private String[] words = new String[]{"foo", "bar", "baz", "beer", "wine", "cheese"};
+        private static final String[] WORDS = new String[]{"foo", "bar", "baz", "beer", "wine", "cheese"};
         private int counter;
+        private final Random random = new Random();
 
         public int increment() {
             return ++counter;
         }
 
         public String word() {
-            int ran = new Random().nextInt(words.length);
-            return words[ran];
+            return WORDS[random.nextInt(WORDS.length)];
         }
 
         @Override
@@ -71,7 +76,7 @@ public final class MyClient {
                 // after it has built this special timeout error message body
                 .setBody(simple("#${header.corId}:${header.word}-Time out error!!!"));
 
-            from("timer:trigger")
+            from("timer:trigger").id("client")
                 // set correlation id as unique incrementing number
                 .setHeader("corId", method(this, "increment"))
                 // set random word to use in request
@@ -81,7 +86,7 @@ public final class MyClient {
                 // log request before
                 .log("Request:  ${id}:${body}")
                 // call netty server using a single shared connection and using custom correlation manager
-                // to ensure we can correltly map the request and response pairs
+                // to ensure we can correctly map the request and response pairs
                 .to("netty:tcp://localhost:4444?sync=true&encoders=#bean:myEncoder&decoders=#bean:myDecoder"
                     + "&producerPoolEnabled=false&correlationManager=#bean:myManager")
                 // log response after
diff --git a/examples/netty-custom-correlation/src/main/java/org/apache/camel/example/netty/MyServer.java b/examples/netty-custom-correlation/src/main/java/org/apache/camel/example/netty/MyServer.java
index 57a94d9..3a2679b 100644
--- a/examples/netty-custom-correlation/src/main/java/org/apache/camel/example/netty/MyServer.java
+++ b/examples/netty-custom-correlation/src/main/java/org/apache/camel/example/netty/MyServer.java
@@ -35,16 +35,16 @@ public final class MyServer {
         main.run(args);
     }
 
-    private static class MyRouteBuilder extends RouteBuilder {
+    static class MyRouteBuilder extends RouteBuilder {
 
         @Override
         public void configure() throws Exception {
-            from("netty:tcp://localhost:4444?sync=true&encoders=#bean:myEncoder&decoders=#bean:myDecoder")
+            from("netty:tcp://localhost:4444?sync=true&encoders=#bean:myEncoder&decoders=#bean:myDecoder").id("server")
                 .log("Request:  ${id}:${body}")
                 .filter(simple("${body} contains 'beer'"))
                     // use some delay when its beer to make responses interleaved
                     // and make the delay asynchronous
-                    .delay(simple("${random(1000,9000)}")).asyncDelayed().end()
+                    .delay(simple("${random(1000,2000)}")).asyncDelayed().end()
                 .end()
                 .transform(simple("${body}-Echo"))
                 .log("Response: ${id}:${body}");
diff --git a/examples/netty-custom-correlation/src/test/java/org/apache/camel/example/netty/NettyTest.java b/examples/netty-custom-correlation/src/test/java/org/apache/camel/example/netty/NettyTest.java
new file mode 100644
index 0000000..4e30878
--- /dev/null
+++ b/examples/netty-custom-correlation/src/test/java/org/apache/camel/example/netty/NettyTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.example.netty;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Predicate;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.camel.example.netty.MyClient.createCorrelationManager;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A unit test checking that Camel can communicate over TCP with Netty using a custom codec.
+ */
+class NettyTest extends CamelTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext camelContext = super.createCamelContext();
+        // Bind the Correlation Manager to use for the test
+        camelContext.getRegistry().bind("myManager", createCorrelationManager());
+        // Bind the custom codec
+        camelContext.getRegistry().bind("myEncoder", new MyCodecEncoderFactory());
+        camelContext.getRegistry().bind("myDecoder", new MyCodecDecoderFactory());
+        return camelContext;
+    }
+
+    @Test
+    void should_exchange_messages_over_tcp_using_a_custom_codec() {
+        Predicate isAGeneratedWord = exchange -> exchange.getIn().getBody(String.class).endsWith("-Echo");
+        NotifyBuilder notify = new NotifyBuilder(context).fromRoute("client")
+            .whenCompleted(5).whenAllDoneMatches(isAGeneratedWord)
+            .and().fromRoute("server").whenCompleted(5).whenAllDoneMatches(isAGeneratedWord).create();
+        assertTrue(
+            notify.matches(20, TimeUnit.SECONDS), "5 messages should be exchanged"
+        );
+    }
+
+    @Override
+    protected RoutesBuilder[] createRouteBuilders() {
+        return new RoutesBuilder[]{new MyServer.MyRouteBuilder(), new MyClient.MyRouteBuilder()};
+    }
+}
diff --git a/examples/oaipmh/README.adoc b/examples/oaipmh/README.adoc
index d5d0970..868bf3f 100644
--- a/examples/oaipmh/README.adoc
+++ b/examples/oaipmh/README.adoc
@@ -2,12 +2,21 @@
 
 This example shows how to use the consumer and producer component to harvest data from a repository using the OAIPMH protocol. The repository to be harvested is ArXiv, which contains preprint information for scientific articles and is widely known on the web. The example first queries the collections available in the repository. From the available collections, it filters those that are identified as "Mathematics" and from their identifier it extracts the titles of the publications contai [...]
 
+=== Build
+
+You will need to compile this example first:
+
+[source,sh]
+----
+$ mvn compile
+----
+
 === How to run
 
 You can run this example using
 
 ----
-$ mvn compile exec:java
+$ mvn exec:java
 ----
 
 == More information about the camel-oaipmh Component.
diff --git a/examples/oaipmh/pom.xml b/examples/oaipmh/pom.xml
index 8fa3981..82854b9 100644
--- a/examples/oaipmh/pom.xml
+++ b/examples/oaipmh/pom.xml
@@ -68,6 +68,12 @@
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-oaipmh</artifactId>
         </dependency>
+        <!-- for testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 
diff --git a/examples/oaipmh/src/main/java/org/apache/camel/example/oaipmh/Application.java b/examples/oaipmh/src/main/java/org/apache/camel/example/oaipmh/Application.java
index 8b0ee82..a3ac008 100644
--- a/examples/oaipmh/src/main/java/org/apache/camel/example/oaipmh/Application.java
+++ b/examples/oaipmh/src/main/java/org/apache/camel/example/oaipmh/Application.java
@@ -25,14 +25,12 @@ public final class Application {
     }
 
     public static void main(String[] args) throws Exception {
-        CamelContext context = new DefaultCamelContext();
-        context.addRoutes(new OAIPMHRouteBuilder());
-        context.start();
-        // so run for 10 seconds
-        Thread.sleep(10000);
-
-        // and then stop nicely
-        context.stop();
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.addRoutes(new OAIPMHRouteBuilder());
+            context.start();
+            // so run for 10 seconds
+            Thread.sleep(10_000);
+        }
     }
 
 }
diff --git a/examples/oaipmh/src/main/java/org/apache/camel/example/oaipmh/OAIPMHRouteBuilder.java b/examples/oaipmh/src/main/java/org/apache/camel/example/oaipmh/OAIPMHRouteBuilder.java
index 711733c..1f7a2a1 100644
--- a/examples/oaipmh/src/main/java/org/apache/camel/example/oaipmh/OAIPMHRouteBuilder.java
+++ b/examples/oaipmh/src/main/java/org/apache/camel/example/oaipmh/OAIPMHRouteBuilder.java
@@ -16,17 +16,11 @@
  */
 package org.apache.camel.example.oaipmh;
 
-
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.support.builder.Namespaces;
 
-
-
-
 public class OAIPMHRouteBuilder extends RouteBuilder {
 
-    
-
     @Override
     public void configure() {
 
@@ -41,9 +35,7 @@ public class OAIPMHRouteBuilder extends RouteBuilder {
                         new Namespaces("default", "http://www.openarchives.org/OAI/2.0/"))
                 .to ("direct:ListRecords");
                 
-                
-                
-                from("direct:ListRecords")
+        from("direct:ListRecords")
                 .setHeader("CamelOaimphSet", xpath("/default:set/default:setSpec/text()",
                          new Namespaces("default", "http://www.openarchives.org/OAI/2.0/")))
                 //Prevent error message by request overload
diff --git a/examples/oaipmh/src/main/java/org/apache/camel/example/oaipmh/Application.java b/examples/oaipmh/src/test/java/org/apache/camel/example/oaipmh/OAIPMHTest.java
similarity index 52%
copy from examples/oaipmh/src/main/java/org/apache/camel/example/oaipmh/Application.java
copy to examples/oaipmh/src/test/java/org/apache/camel/example/oaipmh/OAIPMHTest.java
index 8b0ee82..2b27e58 100644
--- a/examples/oaipmh/src/main/java/org/apache/camel/example/oaipmh/Application.java
+++ b/examples/oaipmh/src/test/java/org/apache/camel/example/oaipmh/OAIPMHTest.java
@@ -16,23 +16,30 @@
  */
 package org.apache.camel.example.oaipmh;
 
-import org.apache.camel.CamelContext;
-import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
 
-public final class Application {
-    
-    private Application() {
-    }
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
-    public static void main(String[] args) throws Exception {
-        CamelContext context = new DefaultCamelContext();
-        context.addRoutes(new OAIPMHRouteBuilder());
-        context.start();
-        // so run for 10 seconds
-        Thread.sleep(10000);
+/**
+ * A unit test checking that Camel can extract data using OAI-PMH.
+ */
+class OAIPMHTest extends CamelTestSupport {
 
-        // and then stop nicely
-        context.stop();
+    @Test
+    void should_extract_title_publications() {
+        NotifyBuilder notify = new NotifyBuilder(context).wereSentTo("log:titles").whenCompleted(1).create();
+        assertTrue(
+            notify.matches(30, TimeUnit.SECONDS), "at least 1 message should be completed"
+        );
     }
 
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new OAIPMHRouteBuilder();
+    }
 }