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

[camel] branch main updated: CAMEL-17690: Add support of Nested tests (#7063)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new 494fd84  CAMEL-17690: Add support of Nested tests (#7063)
494fd84 is described below

commit 494fd84a82ab3fd980b1cd09f87fef0d4456d7c0
Author: Nicolas Filotto <es...@users.noreply.github.com>
AuthorDate: Tue Mar 1 11:36:00 2022 +0100

    CAMEL-17690: Add support of Nested tests (#7063)
    
    ## Motivation
    
    Some end-users could want to organize their tests using Nested tests thus it could be interesting to support them at least in the annotated approach.
    
    ## Modifications
    
    * Consider the annotations on all the nested test classes instead of the only the current class
    * Adapt the code to add the values of all **multivalued** attributes ordered from the outermost to the innermost
    * Adapt the code to use only the value of the innermost for **mono-valued** attributes including `propertyPlaceholderLocations` as it is coupled to `propertyPlaceholderFileName`
    * Adapt the code to support the annotations `ReplaceInRegistry` and `Configure` in nested test classes
    * Fix the expected format of the attributes `properties` and `replaceRouteFromWith` to have `"x1=y1", "x2=y2", ...` instead of   `"x1"="y1", "x2"="y2", ...`  (not related to the initial topic)
---
 .../src/main/docs/test-main-junit5.adoc            |  73 +++++++-
 .../camel/test/main/junit5/CamelMainContext.java   | 198 +++++++++++++--------
 .../camel/test/main/junit5/CamelMainExtension.java |  27 ++-
 .../camel/test/main/junit5/CamelMainTest.java      |  48 ++++-
 .../apache/camel/test/main/junit5/Configure.java   |   5 +-
 .../camel/test/main/junit5/DebuggerCallback.java   |   3 +
 .../camel/test/main/junit5/ReplaceInRegistry.java  |   9 +-
 .../main/junit5/annotation/AdviceRouteTest.java    |  25 +++
 .../{AdviceRouteTest.java => ConfigureTest.java}   |  32 ++--
 .../junit5/annotation/DumpRouteCoverageTest.java   |  10 ++
 .../main/junit5/annotation/InheritanceTest.java    |  10 ++
 .../main/junit5/annotation/MockEndpointTest.java   |  46 ++++-
 .../annotation/MockEndpointsAndSkipTest.java       |  39 +++-
 .../junit5/annotation/OverridePropertiesTest.java  |  31 +++-
 .../annotation/ReplaceBeanFromFieldTest.java       |  16 ++
 .../annotation/ReplaceBeanFromMethodTest.java      |  18 ++
 .../junit5/annotation/ReplaceRouteFromTest.java    |  19 +-
 .../annotation/SupportParameterizedTest.java       |  11 ++
 .../junit5/annotation/SupportRepeatedTest.java     |  10 ++
 .../junit5/annotation/TestInstanceDefaultTest.java |  24 +++
 .../annotation/TestInstancePerClassTest.java       |  24 +++
 .../annotation/TestInstancePerMethodTest.java      |  24 +++
 ...UseSeveralPropertyPlaceholderLocationsTest.java |  14 ++
 .../annotation/WithConfigurationClassTest.java     |  15 ++
 .../annotation/WithDebuggerCallbackTest.java       |  10 ++
 .../main/junit5/annotation/WithMainClassTest.java  |  33 ++++
 .../LoadCustomConfigurationDefaultPackageTest.java |  14 ++
 .../LoadCustomConfigurationSamePackageTest.java    |  10 ++
 .../MyOtherConfiguration.java}                     |  28 +--
 .../MyOtherMainClass.java}                         |  19 +-
 .../ConfigureTest.java}                            |  25 +--
 31 files changed, 699 insertions(+), 171 deletions(-)

diff --git a/components/camel-test/camel-test-main-junit5/src/main/docs/test-main-junit5.adoc b/components/camel-test/camel-test-main-junit5/src/main/docs/test-main-junit5.adoc
index 1c53888..cbdd133 100644
--- a/components/camel-test/camel-test-main-junit5/src/main/docs/test-main-junit5.adoc
+++ b/components/camel-test/camel-test-main-junit5/src/main/docs/test-main-junit5.adoc
@@ -286,7 +286,7 @@ Some properties are inherited from properties file like the `application.propert
 The method `useOverridePropertiesWithPropertiesComponent()` can be overridden to provide an instance of type `java.util.Properties` that contains the properties to override.
 
 === Annotation ===
-The attribute `properties` can be set to provide an array of `String` representing the key/value pairs of properties to override in the following order `property-key-1, property-value-1, property-key-2, ...`.
+The attribute `properties` can be set to provide an array of `String` representing the key/value pairs of properties to override in the following format `"property-key-1=property-value-1", "property-key-2=property-value-1", ...`.
 
 In the next examples, the value of the property whose name is `host` is replaced with `localhost`.
 
@@ -309,7 +309,7 @@ class SomeTest extends CamelMainTestSupport {
 === Annotation ===
 [source,java]
 ----
-@CamelMainTest(properties = { "host", "localhost" })
+@CamelMainTest(properties = { "host=localhost" })
 class SomeTest {
 
     // Rest of the test class
@@ -324,7 +324,7 @@ To be able to test easily the behavior of a route without being affected by the
 The method `replaceRouteFromWith()` can be called to provide the id of the route to modify and the URI of the new from endpoint.
 
 === Annotation ===
-The attribute `replaceRouteFromWith` can be set to provide an array of `String` representing a list of id of the route to modify and the URI of the new from endpoint in the following order `route-id-1, new-uri-1, route-id-2, ...`.
+The attribute `replaceRouteFromWith` can be set to provide an array of `String` representing a list of id of the route to modify and the URI of the new from endpoint in the following format `"route-id-1=new-uri-1", "route-id-2=new-uri-2", ...`.
 
 In the next examples, the route whose id is `main-route` is advised to replace its current from endpoint with a `direct:main` endpoint.
 
@@ -347,7 +347,7 @@ class SomeTest extends CamelMainTestSupport {
 === Annotation ===
 [source,java]
 ----
-@CamelMainTest(replaceRouteFromWith = { "main-route", "direct:main" })
+@CamelMainTest(replaceRouteFromWith = { "main-route=direct:main" })
 class SomeTest {
 
     // Rest of the test class
@@ -518,3 +518,68 @@ The method `isUseDebugger()` can be overridden to return `true` indicating that
 
 === Annotation ===
 The test class needs to implement the interface `org.apache.camel.test.main.junit5.DebuggerCallback` to enable the feature. The methods `debugBefore` and `debugAfter` can then be implemented to execute some specific code for debugging purpose.
+
+== Nested tests
+
+The annotation based approach supports natively https://junit.org/junit5/docs/current/user-guide/#writing-tests-nested[Nested tests]. It is even possible to annotate `@Nested` test class with `@CamelMainTest` to change the configuration inherited from the outer class however please note that not all attributes can be set at nested test class level. Indeed, for the sake of simplicity, the attributes `dumpRouteCoverage` and `shutdownTimeout` can only be set at outer class level.
+
+According to the total amount of values accepted by an attribute, if a `@Nested` test class set this attribute, the behavior can change:
+
+ * In case of *multivalued* attributes like `properties`, `replaceRouteFromWith`, `configurationClasses` and `advices`, the values set on the `@Nested` test class are added to the values of the outer classes, and the resulting values are ordered from outermost to innermost.
+ * In case of *mono-valued* attributes like `mainClass`, `propertyPlaceholderFileName`, `mockEndpoints` and `mockEndpointsAndSkip`, the value set on the innermost class is used.
+
+The only exception is the attribute `propertyPlaceholderLocations` that behaves like a mono-valued attribute because it is tightly coupled with `propertyPlaceholderFileName` so it must have the same behavior for the sake of consistency.
+
+To have a better understanding of the behavior for each type of attribute, please check the following examples:
+
+=== Multivalued ===
+In the next example, the property `some-property` is set to `foo` for all the tests in `SomeTest` including the tests in `SomeNestedTest`. Additionally, the property `some-other-property` is set to `bar` but only for all the tests in `SomeNestedTest`.
+
+[source,java]
+----
+@CamelMainTest(properties = { "some-property=foo" })
+class SomeTest {
+
+    @Nested
+    @CamelMainTest(properties = { "some-other-property=bar" })
+    class SomeNestedTest {
+
+        // Rest of the nested test class
+    }
+
+    // Rest of the test class
+}
+----
+
+=== Mono-valued ===
+In the next example, `SomeMainClass` is used as main class for all the tests directly inside `SomeTest` but also the tests in the `@Nested` test class `SomeOtherNestedTest` as it is not redefined. `SomeOtherMainClass` is used as main class for all the tests directly inside `SomeNestedTest` but also the tests in the `@Nested` test class `SomeDeeplyNestedTest` as it is not redefined.
+
+[source,java]
+----
+@CamelMainTest(mainClass = SomeMainClass.class)
+class SomeTest {
+
+    @CamelMainTest(mainClass = SomeOtherMainClass.class)
+    @Nested
+    class SomeNestedTest {
+
+        @Nested
+        class SomeDeeplyNestedTest {
+
+           // Rest of the nested test class
+        }
+
+       // Rest of the nested test class
+    }
+
+    @Nested
+    class SomeOtherNestedTest {
+
+       // Rest of the nested test class
+    }
+
+    // Rest of the test class
+}
+----
+
+The annotations `@Configure` and `@ReplaceInRegistry` can also be used on methods or fields inside `@Nested` test classes knowing that the annotations of outer classes are processed before the annotations of inner classes.
diff --git a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainContext.java b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainContext.java
index fd9de35..da0641f 100644
--- a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainContext.java
+++ b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainContext.java
@@ -19,6 +19,8 @@ package org.apache.camel.test.main.junit5;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.List;
+import java.util.stream.Collectors;
 
 import org.apache.camel.CamelConfiguration;
 import org.apache.camel.Exchange;
@@ -100,17 +102,13 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
     static final class Builder {
 
         /**
-         * The type of the test class to execute.
+         * The annotations {@code CamelMainTest} could be extracted from the test instances.
          */
-        private final Class<?> requiredTestClass;
+        private final List<CamelMainTest> annotations;
         /**
-         * The annotation {@code CamelMainTest} extracted from the test class to execute.
+         * The test instances, ordered from outermost to innermost.
          */
-        private final CamelMainTest annotation;
-        /**
-         * The instance of the test class to execute.
-         */
-        private final Object instance;
+        private final List<Object> instances;
         /**
          * The flag indicating whether JMX should be enabled.
          */
@@ -123,9 +121,12 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
          *                {@code CamelMainContext} is extracted
          */
         private Builder(ExtensionContext context) {
-            this.requiredTestClass = context.getRequiredTestClass();
-            this.annotation = requiredTestClass.getAnnotation(CamelMainTest.class);
-            this.instance = context.getRequiredTestInstance();
+            this.instances = context.getRequiredTestInstances().getAllInstances();
+            this.annotations = instances.stream()
+                    .map(Object::getClass)
+                    .filter(tClass -> tClass.isAnnotationPresent(CamelMainTest.class))
+                    .map(tClass -> tClass.getAnnotation(CamelMainTest.class))
+                    .collect(Collectors.toList());
         }
 
         Builder useJmx(boolean useJmx) {
@@ -148,24 +149,33 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
             configureDebuggerIfNeeded(camelContext);
             initCamelContext(camelContext);
             final CamelBeanPostProcessor beanPostProcessor = extendedCamelContext.getBeanPostProcessor();
-            initInstance(beanPostProcessor);
-            replaceBeansInRegistry(camelContext.getRegistry());
-            applyReplaceRouteFromWith(camelContext);
-            adviceRoutes(camelContext, beanPostProcessor);
+            for (Object instance : instances) {
+                initInstance(beanPostProcessor, instance);
+                replaceBeansInRegistry(camelContext.getRegistry(), instance);
+            }
+            for (CamelMainTest annotation : annotations) {
+                applyReplaceRouteFromWith(camelContext, annotation);
+                adviceRoutes(camelContext, beanPostProcessor, annotation);
+            }
             return new CamelMainContext(camelContext);
         }
 
         /**
          * Configure the shutdown timeout to avoid waiting for too long.
+         * <p/>
+         * In case of {@code @Nested} test classes, the value is always extracted from the annotation of the outer
+         * class.
          */
         private void configureShutdownTimeout(ModelCamelContext context) {
-            context.getShutdownStrategy().setTimeout(annotation.shutdownTimeout());
+            // Get the value of the attribute from the annotation of the outer class
+            context.getShutdownStrategy().setTimeout(getOuterClassAnnotation().shutdownTimeout());
         }
 
         /**
-         * Inject all the Camel related object instances into the test instance.
+         * Inject all the Camel related object instances into the given test instance.
          */
-        private void initInstance(CamelBeanPostProcessor beanPostProcessor) throws Exception {
+        private void initInstance(CamelBeanPostProcessor beanPostProcessor, Object instance) throws Exception {
+            final Class<?> requiredTestClass = instance.getClass();
             beanPostProcessor.postProcessBeforeInitialization(instance, requiredTestClass.getName());
             beanPostProcessor.postProcessAfterInitialization(instance, requiredTestClass.getName());
         }
@@ -173,16 +183,26 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
         /**
          * Mock the endpoints corresponding to the patterns provided by {@link CamelMainTest#mockEndpoints()} and
          * {@link CamelMainTest#mockEndpointsAndSkip()} if any.
+         * <p/>
+         * {@code @Nested} test classes can configure patterns to mock endpoints. The value of those attributes set on
+         * the innermost class is used.
          */
         private void mockEndpointsIfNeeded(ExtendedCamelContext context) {
-            // enable auto mocking if enabled
-            final String mockEndpoints = annotation.mockEndpoints();
-            if (!mockEndpoints.isEmpty()) {
-                context.registerEndpointCallback(new InterceptSendToMockEndpointStrategy(mockEndpoints));
-            }
-            final String mockEndpointsAndSkip = annotation.mockEndpointsAndSkip();
-            if (!mockEndpointsAndSkip.isEmpty()) {
-                context.registerEndpointCallback(new InterceptSendToMockEndpointStrategy(mockEndpointsAndSkip, true));
+            boolean mockEndpointsSet = false;
+            boolean mockEndpointsAndSkipSet = false;
+            for (int i = annotations.size() - 1; i >= 0; i--) {
+                final CamelMainTest annotation = annotations.get(i);
+                // enable auto mocking if enabled
+                final String mockEndpoints = annotation.mockEndpoints();
+                if (!mockEndpointsSet && !mockEndpoints.isEmpty()) {
+                    mockEndpointsSet = true;
+                    context.registerEndpointCallback(new InterceptSendToMockEndpointStrategy(mockEndpoints));
+                }
+                final String mockEndpointsAndSkip = annotation.mockEndpointsAndSkip();
+                if (!mockEndpointsAndSkipSet && !mockEndpointsAndSkip.isEmpty()) {
+                    mockEndpointsAndSkipSet = true;
+                    context.registerEndpointCallback(new InterceptSendToMockEndpointStrategy(mockEndpointsAndSkip, true));
+                }
             }
         }
 
@@ -190,8 +210,13 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
          * Configure the debug mode if the test instance is of type {@link DebuggerCallback} in a such way that the
          * callback methods {@link DebuggerCallback#debugBefore} and {@link DebuggerCallback#debugAfter} are called when
          * executing the routes.
+         * <p/>
+         * In case of {@code @Nested} test classes, the instance used to check if the debug mode needs to be enabled is
+         * the instance of the outer class.
          */
         private void configureDebuggerIfNeeded(ModelCamelContext context) {
+            // Get the instance of the outer class
+            Object instance = getOuterClassInstance();
             if (instance instanceof DebuggerCallback) {
                 context.setDebugging(true);
                 context.setDebugger(new DefaultDebugger());
@@ -226,19 +251,25 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
          */
         private MainForTest createMainForTest() {
             final MainForTest main = new MainForTest();
-            configureMainClass(main);
-            addConfigurationClasses(main);
             main.configure().setJmxEnabled(useJmx);
-            configureOverrideProperties(main);
-            configurePropertyPlaceholderLocations(main);
-            invokeConfigureMethods(main);
+            for (CamelMainTest annotation : annotations) {
+                configureMainClass(main, annotation);
+                addConfigurationClasses(main, annotation);
+                configureOverrideProperties(main, annotation);
+                configurePropertyPlaceholderLocations(main, annotation);
+            }
+            for (Object instance : instances) {
+                invokeConfigureMethods(main, instance);
+            }
             return main;
         }
 
         /**
          * Configure the main class to use in the given Camel Main application for test.
+         * <p/>
+         * {@code @Nested} test classes can configure a main class.
          */
-        private void configureMainClass(MainForTest main) {
+        private void configureMainClass(MainForTest main, CamelMainTest annotation) {
             final Class<?> mainClass = annotation.mainClass();
             if (mainClass != void.class) {
                 main.configure().withBasePackageScan(mainClass.getPackageName());
@@ -247,8 +278,10 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
 
         /**
          * Add the additional configuration classes to the global configuration.
+         * <p/>
+         * {@code @Nested} test classes can provide additional configuration classes.
          */
-        private void addConfigurationClasses(MainForTest main) {
+        private void addConfigurationClasses(MainForTest main, CamelMainTest annotation) {
             // Add the configuration classes if any
             for (Class<? extends CamelConfiguration> configurationClass : annotation.configurationClasses()) {
                 main.configure().addConfiguration(configurationClass);
@@ -260,10 +293,13 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
          * using the default constructor, then inject all the Camel related object instances into the created instances
          * and finally use these created instances to advice the routes corresponding to
          * {@link AdviceRouteMapping#route()}.
+         * <p/>
+         * {@code @Nested} test classes can provide additional advices.
          *
          * @throws Exception if a route builder could not be created or initialized, or if a route could not be advised.
          */
-        private void adviceRoutes(ModelCamelContext context, CamelBeanPostProcessor beanPostProcessor) throws Exception {
+        private void adviceRoutes(ModelCamelContext context, CamelBeanPostProcessor beanPostProcessor, CamelMainTest annotation)
+                throws Exception {
             for (AdviceRouteMapping adviceRouteMapping : annotation.advices()) {
                 final Class<? extends RouteBuilder> adviceClass = adviceRouteMapping.advice();
                 try {
@@ -290,26 +326,28 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
 
         /**
          * For all uris and ids extracted from {@link CamelMainTest#replaceRouteFromWith()}, replace the from endpoint
-         * in the route corresponding to the extracted id with the extracted uri knowing that the expected order is
-         * {@code route-id-1, new-uri-1, route-id-2, ...}.
-         * 
+         * in the route corresponding to the extracted id with the extracted uri knowing that the expected format is
+         * {@code "route-id-1=new-uri-1", "route-id-2=new-uri-1", ...}.
+         * <p/>
+         * {@code @Nested} test classes can provide additional uris and ids.
+         *
          * @throws Exception if the content of {@link CamelMainTest#replaceRouteFromWith()} doesn't have the expected
-         *                   length or a route could not be advised.
+         *                   format or a route could not be advised.
          */
-        private void applyReplaceRouteFromWith(ModelCamelContext context) throws Exception {
-            String[] fromEndpoints = annotation.replaceRouteFromWith();
-            if (fromEndpoints.length % 2 == 1) {
-                throw new RuntimeCamelException(
-                        "The length of the array of replaceRouteFromWith should be even, as we expect a route id followed by a uri");
-            }
-            for (int i = 0; i < fromEndpoints.length - 1; i += 2) {
-                final String uri = fromEndpoints[i + 1];
-                AdviceWith.adviceWith(context.getRouteDefinition(fromEndpoints[i]), context, new AdviceWithRouteBuilder() {
-                    @Override
-                    public void configure() {
-                        replaceFromWith(uri);
-                    }
-                });
+        private void applyReplaceRouteFromWith(ModelCamelContext context, CamelMainTest annotation) throws Exception {
+            for (final String fromEndpoint : annotation.replaceRouteFromWith()) {
+                final int index = fromEndpoint.indexOf('=');
+                if (index == -1) {
+                    throw new RuntimeCamelException(
+                            "The attribute replaceRouteFromWith doesn't have the expected format, it should be of type \"route-id-1=new-uri-1\", ...");
+                }
+                AdviceWith.adviceWith(context.getRouteDefinition(fromEndpoint.substring(0, index)), context,
+                        new AdviceWithRouteBuilder() {
+                            @Override
+                            public void configure() {
+                                replaceFromWith(fromEndpoint.substring(index + 1));
+                            }
+                        });
             }
         }
 
@@ -320,7 +358,8 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
          * @throws RuntimeCamelException if an annotated method could not be invoked or an annotated field cannot be
          *                               accessed, or if the annotated method has parameters.
          */
-        private void replaceBeansInRegistry(Registry registry) {
+        private void replaceBeansInRegistry(Registry registry, Object instance) {
+            final Class<?> requiredTestClass = instance.getClass();
             for (Method method : findAnnotatedMethods(requiredTestClass, ReplaceInRegistry.class,
                     HierarchyTraversalMode.TOP_DOWN)) {
                 Class<?>[] parameterTypes = method.getParameterTypes();
@@ -358,8 +397,8 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
          * 
          * @throws RuntimeCamelException if an annotated method could not be invoked or has invalid parameters.
          */
-        private void invokeConfigureMethods(MainForTest main) {
-            for (Method method : findAnnotatedMethods(requiredTestClass, Configure.class, HierarchyTraversalMode.TOP_DOWN)) {
+        private void invokeConfigureMethods(MainForTest main, Object instance) {
+            for (Method method : findAnnotatedMethods(instance.getClass(), Configure.class, HierarchyTraversalMode.TOP_DOWN)) {
                 Class<?>[] parameterTypes = method.getParameterTypes();
                 if (parameterTypes.length == 1 && parameterTypes[0] == MainConfigurationProperties.class) {
                     if (method.trySetAccessible()) {
@@ -381,19 +420,22 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
 
         /**
          * Configures as override properties all the properties extracted from {@link CamelMainTest#properties()}
-         * knowing that the expected order is {@code property-key-1, property-value-1, property-key-2, ...}.
-         * 
+         * knowing that the expected format is
+         * {@code "property-key-1=property-value-1", "property-key-2=property-value-2", ...}.
+         *
+         * {@code @Nested} test classes can provide properties as override properties.
+         *
          * @throws RuntimeCamelException if the content of {@link CamelMainTest#properties()} doesn't have the expected
-         *                               length.
+         *                               format.
          */
-        private void configureOverrideProperties(MainForTest main) {
-            String[] properties = annotation.properties();
-            if (properties.length % 2 == 1) {
-                throw new RuntimeCamelException(
-                        "The length of the array of properties should be even, as we expect a key followed by its value");
-            }
-            for (int i = 0; i < properties.length - 1; i += 2) {
-                main.addOverrideProperty(properties[i], properties[i + 1]);
+        private void configureOverrideProperties(MainForTest main, CamelMainTest annotation) {
+            for (String property : annotation.properties()) {
+                final int index = property.indexOf('=');
+                if (index == -1) {
+                    throw new RuntimeCamelException(
+                            "The attribute properties doesn't have the expected format, it should be of type \"key-1=value-1\", ...");
+                }
+                main.addOverrideProperty(property.substring(0, index), property.substring(index + 1));
             }
         }
 
@@ -402,19 +444,35 @@ final class CamelMainContext implements ExtensionContext.Store.CloseableResource
          * {@link CamelMainTest#propertyPlaceholderLocations()} if set otherwise from
          * {@link CamelMainTest#propertyPlaceholderFileName()} if set in such way that the locations will be in the
          * package of the test class or in the default package.
+         * <p/>
+         * {@code @Nested} test classes can configure the property placeholder locations.
          */
-        private void configurePropertyPlaceholderLocations(MainForTest main) {
-            String[] locations = annotation.propertyPlaceholderLocations();
+        private void configurePropertyPlaceholderLocations(MainForTest main, CamelMainTest annotation) {
+            final String[] locations = annotation.propertyPlaceholderLocations();
             if (locations.length == 0) {
-                String fileName = annotation.propertyPlaceholderFileName();
+                final String fileName = annotation.propertyPlaceholderFileName();
                 if (!fileName.isEmpty()) {
                     main.setPropertyPlaceholderLocations(
                             String.format("classpath:%s/%s;optional=true,classpath:%s;optional=true",
-                                    requiredTestClass.getPackageName().replace('.', '/'), fileName, fileName));
+                                    getOuterClassInstance().getClass().getPackageName().replace('.', '/'), fileName, fileName));
                 }
             } else {
                 main.setPropertyPlaceholderLocations(String.join(",", locations));
             }
         }
+
+        /**
+         * @return the instance of the outer class.
+         */
+        private Object getOuterClassInstance() {
+            return instances.get(0);
+        }
+
+        /**
+         * @return the annotation of type {@code CamelMainTest} that has been added to the outer class.
+         */
+        private CamelMainTest getOuterClassAnnotation() {
+            return annotations.get(0);
+        }
     }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainExtension.java b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainExtension.java
index 2424dae..524aa53 100644
--- a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainExtension.java
+++ b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainExtension.java
@@ -135,11 +135,11 @@ final class CamelMainExtension
      * Dump the route coverage for the given test if it is enabled.
      */
     private void dumpRouteCoverageIfNeeded(ExtensionContext context, long time, String currentTestName) throws Exception {
-        final Class<?> requiredTestClass = context.getRequiredTestClass();
-        final CamelMainTest annotation = requiredTestClass.getAnnotation(CamelMainTest.class);
         // if we should dump route stats, then write that to a file
-        if (isRouteCoverageEnabled(annotation)) {
-            String className = requiredTestClass.getSimpleName();
+        if (isRouteCoverageEnabled(context)) {
+            final Class<?> requiredTestClass = context.getRequiredTestClass();
+            // In case of a {@code @Nested} test class, its name will be prefixed by the name of its outer classes
+            String className = requiredTestClass.getName().substring(requiredTestClass.getPackageName().length() + 1);
             String dir = "target/camel-route-coverage";
             String name = String.format("%s-%s.xml", className, StringHelper.before(currentTestName, "("));
 
@@ -158,21 +158,16 @@ final class CamelMainExtension
     }
 
     /**
-     * Indicates whether the route coverage is enabled according to the given extension context.
+     * Indicates whether the route coverage is enabled according to the given extension context and the value of the
+     * system property {@link org.apache.camel.test.junit5.CamelTestSupport#ROUTE_COVERAGE_ENABLED}.
+     * <p/>
+     * In case of {@code @Nested} test classes, the value is always extracted from the annotation of the outer class.
      * 
      * @return {@code true} if the route coverage is enabled, {@code false} otherwise.
      */
     private boolean isRouteCoverageEnabled(ExtensionContext context) {
-        return isRouteCoverageEnabled(context.getRequiredTestClass().getAnnotation(CamelMainTest.class));
-    }
-
-    /**
-     * Indicates whether the route coverage is enabled according to the given annotation and the value of the system
-     * property {@link org.apache.camel.test.junit5.CamelTestSupport#ROUTE_COVERAGE_ENABLED}.
-     * 
-     * @return {@code true} if the route coverage is enabled, {@code false} otherwise.
-     */
-    private boolean isRouteCoverageEnabled(CamelMainTest annotation) {
-        return "true".equalsIgnoreCase(System.getProperty(ROUTE_COVERAGE_ENABLED, "false")) || annotation.dumpRouteCoverage();
+        return "true".equalsIgnoreCase(System.getProperty(ROUTE_COVERAGE_ENABLED, "false"))
+                || context.getRequiredTestInstances().getAllInstances().get(0).getClass()
+                        .getAnnotation(CamelMainTest.class).dumpRouteCoverage();
     }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainTest.java b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainTest.java
index 72ab2cc..59554c2 100644
--- a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/CamelMainTest.java
@@ -60,6 +60,9 @@ public @interface CamelMainTest {
     /**
      * Allows to specify the main class of the application to test if needed in order to simulate the same behavior as
      * with {@link org.apache.camel.main.Main#Main(Class)}.
+     * <p/>
+     * <b>Note:</b> This attribute can be set from a {@code @Nested} test classes. The value of this attribute set on
+     * the innermost class is used.
      *
      * @return the main class of the application to test if any. {@code void.class} by default indicating that there is
      *         no specific main class.
@@ -76,15 +79,18 @@ public @interface CamelMainTest {
      * <pre>
      * <code>
      *
-     * &#64;CamelMainTest(properties = { "host", "localhost", "port", "8080" })
+     * &#64;CamelMainTest(properties = { "host=localhost", "port=8080" })
      * class SomeTest {
      *     // The rest of the test class
      * }
      * </code>
      * </pre>
-     * 
-     * @return an array of {@code String} in the following order
-     *         {@code property-key-1, property-value-1, property-key-2, ...}
+     * <p/>
+     * <b>Note:</b> This attribute can be set from a {@code @Nested} test classes. The values of this attribute are
+     * added to the values of the outer classes, knowing that the values are ordered from outermost to innermost.
+     *
+     * @return an array of {@code String} in the following format
+     *         {@code "property-key-1=property-value-1", "property-key-2=property-value-2", ...}
      */
     String[] properties() default {};
 
@@ -98,14 +104,18 @@ public @interface CamelMainTest {
      * <pre>
      * <code>
      *
-     * &#64;CamelMainTest(replaceRouteFromWith = { "main-route", "direct:main" })
+     * &#64;CamelMainTest(replaceRouteFromWith = { "main-route=direct:main" })
      * class SomeTest {
      *     // The rest of the test class
      * }
      * </code>
      * </pre>
-     * 
-     * @return an array of {@code String} in the following order {@code route-id-1, new-uri-1, route-id-2, ...}
+     * <p/>
+     * <b>Note:</b> This attribute can be set from a {@code @Nested} test classes. The values of this attribute are
+     * added to the values of the outer classes, knowing that the values are ordered from outermost to innermost.
+     *
+     * @return an array of {@code String} in the following format
+     *         {@code "route-id-1=new-uri-1", "route-id-2=new-uri-2", ...}
      */
     String[] replaceRouteFromWith() default {};
 
@@ -121,6 +131,9 @@ public @interface CamelMainTest {
      * <p>
      * <b>Note:</b> Since the properties files are declared as optional, no exception is raised if they are both absent.
      * <b>Note:</b> If {@link #propertyPlaceholderLocations()} is set, the value of this attribute is ignored.
+     * <p/>
+     * <b>Note:</b> This attribute can be set from a {@code @Nested} test classes. The value of this attribute set on
+     * the innermost class is used.
      *
      * @return the file name of the property placeholder located in the same package as the test class or directly in
      *         the default package. Not set by default.
@@ -135,6 +148,9 @@ public @interface CamelMainTest {
      * the property placeholder and so on.
      * <p>
      * <b>Note:</b> If this attribute is set, the value of {@link #propertyPlaceholderFileName()} is ignored.
+     * <p/>
+     * <b>Note:</b> This attribute can be set from a {@code @Nested} test classes. The value of this attribute set on
+     * the innermost class is used.
      *
      * @return the property placeholder locations to use for the test.
      */
@@ -142,6 +158,9 @@ public @interface CamelMainTest {
 
     /**
      * Gives the additional camel configuration classes to add to the global configuration.
+     * <p/>
+     * <b>Note:</b> This attribute can be set from a {@code @Nested} test classes. The values of this attribute are
+     * added to the values of the outer classes, knowing that the values are ordered from outermost to innermost.
      *
      * @return an array of camel configuration classes.
      */
@@ -150,6 +169,9 @@ public @interface CamelMainTest {
     /**
      * Gives the mappings between the routes to advice and the corresponding route builders to call to advice the
      * routes.
+     * <p/>
+     * <b>Note:</b> This attribute can be set from a {@code @Nested} test classes. The values of this attribute are
+     * added to the values of the outer classes, knowing that the values are ordered from outermost to innermost.
      *
      * @return an array of mapping between route and route builder
      */
@@ -159,6 +181,9 @@ public @interface CamelMainTest {
      * Enable auto mocking endpoints based on the pattern.
      * <p/>
      * Return <tt>*</tt> to mock all endpoints.
+     * <p/>
+     * <b>Note:</b> This attribute can be set from a {@code @Nested} test classes. The value of this attribute set on
+     * the innermost class is used.
      *
      * @see EndpointHelper#matchEndpoint(CamelContext, String, String)
      */
@@ -168,6 +193,9 @@ public @interface CamelMainTest {
      * Enable auto mocking endpoints based on the pattern, and <b>skip</b> sending to original endpoint.
      * <p/>
      * Return <tt>*</tt> to mock all endpoints.
+     * <p/>
+     * <b>Note:</b> This attribute can be set from a {@code @Nested} test classes. The value of this attribute set on
+     * the innermost class is used.
      *
      * @see EndpointHelper#matchEndpoint(CamelContext, String, String)
      */
@@ -181,6 +209,9 @@ public @interface CamelMainTest {
      * <p/>
      * You can also turn on route coverage globally via setting JVM system property
      * <tt>CamelTestRouteCoverage=true</tt>.
+     * <p/>
+     * <b>Note:</b> This attribute can only be set on the outer class, values set on a {@code @Nested} test classes are
+     * ignored.
      *
      * @return <tt>true</tt> to write route coverage status in a xml file in the <tt>target/camel-route-coverage</tt>
      *         directory after the test has finished.
@@ -191,6 +222,9 @@ public @interface CamelMainTest {
      * Returns the timeout to use when shutting down (unit in seconds).
      * <p/>
      * Will default use 10 seconds.
+     * <p/>
+     * <b>Note:</b> This attribute can only be set on the outer class, values set on a {@code @Nested} test classes are
+     * ignored.
      *
      * @return the timeout to use
      */
diff --git a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/Configure.java b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/Configure.java
index f68eb18..3810782 100644
--- a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/Configure.java
+++ b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/Configure.java
@@ -51,7 +51,10 @@ import org.apache.camel.main.MainConfigurationProperties;
  * }
  * </code>
  * </pre>
- * 
+ * <p/>
+ * This annotation can be used in {@code @Nested} test classes. The configure methods of outer classes are executed
+ * before the configure methods of inner classes.
+ *
  * @see MainConfigurationProperties
  */
 @Documented
diff --git a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/DebuggerCallback.java b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/DebuggerCallback.java
index 35d70c6..814e8de 100644
--- a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/DebuggerCallback.java
+++ b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/DebuggerCallback.java
@@ -23,6 +23,9 @@ import org.apache.camel.model.ProcessorDefinition;
 /**
  * {@code DebuggerCallback} is an interface to implement in case a test wishes to be called <strong>immediately</strong>
  * before and after invoking a processor by enabling and configuring automatically the debug mode.
+ * <p/>
+ * Only an outer class can implement this interface, implementing this interface from a {@code @Nested} test class has
+ * no effect.
  */
 public interface DebuggerCallback {
 
diff --git a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/ReplaceInRegistry.java b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/ReplaceInRegistry.java
index 53e96b1..f8581d1 100644
--- a/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/ReplaceInRegistry.java
+++ b/components/camel-test/camel-test-main-junit5/src/main/java/org/apache/camel/test/main/junit5/ReplaceInRegistry.java
@@ -24,9 +24,9 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
- * {@code @ReplaceInRegistry} is annotation used to mark all the methods and fields that the test framework should
- * consider replacing existing beans in the registry. It is meant to be used to replace a real implementation of a
- * service with a mock or a test implementation.
+ * {@code @ReplaceInRegistry} is an annotation used to mark all the methods and fields whose return value or value
+ * should replace an existing bean in the registry. It is meant to be used to replace a real implementation of a service
+ * with a mock or a test implementation.
  * <p/>
  * If a field is marked with the annotation {@code @ReplaceInRegistry}, the name and the type of the field are used to
  * identify the bean to replace, and the value of the field is the new value of the bean. The field can be in the test
@@ -76,6 +76,9 @@ import java.lang.annotation.Target;
  * }
  * </code>
  * </pre>
+ * <p/>
+ * This annotation can be used in {@code @Nested} test classes. The {@code @ReplaceInRegistry} annotations of outer
+ * classes are processed before the {@code @ReplaceInRegistry} annotations of inner classes.
  */
 @Documented
 @Inherited
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/AdviceRouteTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/AdviceRouteTest.java
index 2bcb167..5678ef18 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/AdviceRouteTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/AdviceRouteTest.java
@@ -25,6 +25,7 @@ import org.apache.camel.test.main.junit5.AdviceRouteMapping;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.Configure;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -55,6 +56,22 @@ class AdviceRouteTest {
         assertEquals("Hello Will!", result);
     }
 
+    @CamelMainTest(advices = @AdviceRouteMapping(route = "foo", advice = AdviceRouteTest.TestBuilder2.class))
+    @Nested
+    class NestedTest {
+
+        @EndpointInject("direct:bar")
+        ProducerTemplate templateBar;
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            mock.expectedBodiesReceived("Hello Will!");
+            String result = templateBar.requestBody((Object) null, String.class);
+            mock.assertIsSatisfied();
+            assertEquals("Hello Will!", result);
+        }
+    }
+
     static class TestBuilder extends AdviceWithRouteBuilder {
 
         @Override
@@ -62,4 +79,12 @@ class AdviceRouteTest {
             replaceFromWith("direct:foo");
         }
     }
+
+    static class TestBuilder2 extends AdviceWithRouteBuilder {
+
+        @Override
+        public void configure() throws Exception {
+            replaceFromWith("direct:bar");
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/AdviceRouteTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ConfigureTest.java
similarity index 68%
copy from components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/AdviceRouteTest.java
copy to components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ConfigureTest.java
index 2bcb167..4f83563 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/AdviceRouteTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ConfigureTest.java
@@ -18,27 +18,27 @@ package org.apache.camel.test.main.junit5.annotation;
 
 import org.apache.camel.EndpointInject;
 import org.apache.camel.ProducerTemplate;
-import org.apache.camel.builder.AdviceWithRouteBuilder;
 import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.main.MainConfigurationProperties;
-import org.apache.camel.test.main.junit5.AdviceRouteMapping;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.Configure;
+import org.apache.camel.test.main.junit5.annotation.other.MyOtherConfiguration;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
- * A test class ensuring that a route can be advised.
+ * A test class ensuring that a Camel context can be configured.
  */
-@CamelMainTest(advices = @AdviceRouteMapping(route = "foo", advice = AdviceRouteTest.TestBuilder.class))
-class AdviceRouteTest {
+@CamelMainTest
+class ConfigureTest {
 
     @EndpointInject("mock:out")
     MockEndpoint mock;
 
-    @EndpointInject("direct:foo")
+    @EndpointInject("direct:in")
     ProducerTemplate template;
 
     @Configure
@@ -48,18 +48,28 @@ class AdviceRouteTest {
     }
 
     @Test
-    void shouldAdviceTheRoute() throws Exception {
+    void shouldConfigureTheCamelContext() throws Exception {
         mock.expectedBodiesReceived("Hello Will!");
         String result = template.requestBody((Object) null, String.class);
         mock.assertIsSatisfied();
         assertEquals("Hello Will!", result);
     }
 
-    static class TestBuilder extends AdviceWithRouteBuilder {
+    @Nested
+    class NestedTest {
 
-        @Override
-        public void configure() throws Exception {
-            replaceFromWith("direct:foo");
+        @Configure
+        protected void configure(MainConfigurationProperties configuration) {
+            // Add the configuration class
+            configuration.addConfiguration(MyOtherConfiguration.class);
+        }
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            mock.expectedBodiesReceived("Hello Mark!");
+            String result = template.requestBody((Object) null, String.class);
+            mock.assertIsSatisfied();
+            assertEquals("Hello Mark!", result);
         }
     }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/DumpRouteCoverageTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/DumpRouteCoverageTest.java
index 530ae1f..507a191 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/DumpRouteCoverageTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/DumpRouteCoverageTest.java
@@ -23,6 +23,7 @@ import org.apache.camel.main.MainConfigurationProperties;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.Configure;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -52,4 +53,13 @@ class DumpRouteCoverageTest {
         mock.assertIsSatisfied();
         assertEquals("Hello Will!", result);
     }
+
+    @Nested
+    class NestedTest {
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            shouldDumpTheRouteCoverage();
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/InheritanceTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/InheritanceTest.java
index 3bcf657..69c2a87 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/InheritanceTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/InheritanceTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.test.main.junit5.annotation;
 
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -32,4 +33,13 @@ class InheritanceTest extends AdviceRouteTest {
         mock.assertIsSatisfied();
         assertEquals("Hello Will!", result);
     }
+
+    @Nested
+    class NestedTest {
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            shouldAdviceTheRoute();
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/MockEndpointTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/MockEndpointTest.java
index 3967ac4..98d0c3d 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/MockEndpointTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/MockEndpointTest.java
@@ -25,10 +25,12 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.main.MainConfigurationProperties;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.Configure;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.apache.camel.component.mock.MockEndpoint.assertIsSatisfied;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 /**
  * Test ensuring that endpoints matching with a pattern can be mocked.
@@ -36,15 +38,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 @CamelMainTest(mockEndpoints = "*")
 class MockEndpointTest {
 
-    @EndpointInject("mock:direct:start")
-    MockEndpoint mock1;
-    @EndpointInject("mock:direct:foo")
-    MockEndpoint mock2;
-    @EndpointInject("mock:log:foo")
-    MockEndpoint mock3;
-    @EndpointInject("mock:result")
-    MockEndpoint mock4;
-
     @EndpointInject("direct:start")
     ProducerTemplate template;
 
@@ -61,6 +54,10 @@ class MockEndpointTest {
     void shouldMockEndpoints() throws Exception {
         // notice we have automatically mocked all endpoints and the name of the
         // endpoints is "mock:uri"
+        MockEndpoint mock1 = context.getEndpoint("mock:direct:start", MockEndpoint.class);
+        MockEndpoint mock2 = context.getEndpoint("mock:direct:foo", MockEndpoint.class);
+        MockEndpoint mock3 = context.getEndpoint("mock:log:foo", MockEndpoint.class);
+        MockEndpoint mock4 = context.getEndpoint("mock:result", MockEndpoint.class);
         mock1.expectedBodiesReceived("Hello World");
         mock2.expectedBodiesReceived("Hello World");
         mock3.expectedBodiesReceived("Bye World");
@@ -81,6 +78,37 @@ class MockEndpointTest {
         assertNotNull(context.hasEndpoint("mock:log:foo"));
     }
 
+    @CamelMainTest(mockEndpoints = "direct:*")
+    @Nested
+    class NestedTest {
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            // notice we have automatically mocked all direct endpoints and the name of the
+            // endpoints is "mock:uri"
+            MockEndpoint mock1 = context.getEndpoint("mock:direct:start", MockEndpoint.class);
+            MockEndpoint mock2 = context.getEndpoint("mock:direct:foo", MockEndpoint.class);
+            MockEndpoint mock4 = context.getEndpoint("mock:result", MockEndpoint.class);
+            mock1.expectedBodiesReceived("Hello World");
+            mock2.expectedBodiesReceived("Hello World");
+            mock4.expectedBodiesReceived("Bye World");
+
+            template.sendBody("Hello World");
+
+            assertIsSatisfied(context);
+
+            // additional test to ensure correct endpoints in registry
+            assertNotNull(context.hasEndpoint("direct:start"));
+            assertNotNull(context.hasEndpoint("direct:foo"));
+            assertNotNull(context.hasEndpoint("log:foo"));
+            assertNotNull(context.hasEndpoint("mock:result"));
+            // all the endpoints was mocked
+            assertNotNull(context.hasEndpoint("mock:direct:start"));
+            assertNotNull(context.hasEndpoint("mock:direct:foo"));
+            assertNull(context.hasEndpoint("mock:log:foo"));
+        }
+    }
+
     static class MyRouteBuilder extends RouteBuilder {
 
         @Override
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/MockEndpointsAndSkipTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/MockEndpointsAndSkipTest.java
index 9e41716..9c4eb4f 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/MockEndpointsAndSkipTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/MockEndpointsAndSkipTest.java
@@ -26,10 +26,13 @@ import org.apache.camel.component.seda.SedaEndpoint;
 import org.apache.camel.main.MainConfigurationProperties;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.Configure;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.apache.camel.component.mock.MockEndpoint.assertIsSatisfied;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 /**
  * Test ensuring that endpoints matching with a pattern can be mocked and skipped.
@@ -39,8 +42,6 @@ class MockEndpointsAndSkipTest {
 
     @EndpointInject("mock:result")
     MockEndpoint mock1;
-    @EndpointInject("mock:direct:foo")
-    MockEndpoint mock2;
 
     @EndpointInject("direct:start")
     ProducerTemplate template;
@@ -59,6 +60,7 @@ class MockEndpointsAndSkipTest {
         // notice we have automatically mocked the direct:foo endpoints and the name
         // of the endpoints is "mock:uri"
         mock1.expectedBodiesReceived("Hello World");
+        MockEndpoint mock2 = context.getEndpoint("mock:direct:foo", MockEndpoint.class);
         mock2.expectedMessageCount(1);
 
         template.sendBody("Hello World");
@@ -69,6 +71,36 @@ class MockEndpointsAndSkipTest {
         // the seda endpoint
         SedaEndpoint seda = context.getEndpoint("seda:foo", SedaEndpoint.class);
         assertEquals(0, seda.getCurrentQueueSize());
+        assertNotNull(context.hasEndpoint("mock:direct:foo"));
+        assertNull(context.hasEndpoint("mock:direct:foo2"));
+    }
+
+    @CamelMainTest(mockEndpointsAndSkip = "direct:foo2")
+    @Nested
+    class NestedTest {
+
+        @EndpointInject("direct:start2")
+        ProducerTemplate template2;
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            // notice we have automatically mocked the direct:foo endpoints and the name
+            // of the endpoints is "mock:uri"
+            mock1.expectedBodiesReceived("Hello World");
+            MockEndpoint mock2 = context.getEndpoint("mock:direct:foo2", MockEndpoint.class);
+            mock2.expectedMessageCount(1);
+
+            template2.sendBody("Hello World");
+
+            assertIsSatisfied(context);
+
+            // the message was not send to the direct:foo route and thus not sent to
+            // the seda endpoint
+            SedaEndpoint seda = context.getEndpoint("seda:foo", SedaEndpoint.class);
+            assertEquals(0, seda.getCurrentQueueSize());
+            assertNull(context.hasEndpoint("mock:direct:foo"));
+            assertNotNull(context.hasEndpoint("mock:direct:foo2"));
+        }
     }
 
     static class MyRouteBuilder extends RouteBuilder {
@@ -78,6 +110,9 @@ class MockEndpointsAndSkipTest {
             from("direct:start").to("direct:foo").to("mock:result");
 
             from("direct:foo").transform(constant("Bye World")).to("seda:foo");
+            from("direct:start2").to("direct:foo2").to("mock:result");
+
+            from("direct:foo2").transform(constant("Bye World")).to("seda:foo");
         }
     }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/OverridePropertiesTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/OverridePropertiesTest.java
index 2d3f7d1..b35e80a 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/OverridePropertiesTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/OverridePropertiesTest.java
@@ -18,11 +18,13 @@ package org.apache.camel.test.main.junit5.annotation;
 
 import org.apache.camel.EndpointInject;
 import org.apache.camel.ProducerTemplate;
+import org.apache.camel.PropertyInject;
 import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.main.MainConfigurationProperties;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.Configure;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -30,9 +32,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 /**
  * Test ensuring that the default properties can be overridden.
  */
-@CamelMainTest(properties = { "name", "John" })
+@CamelMainTest(properties = "name=John")
 class OverridePropertiesTest {
 
+    @PropertyInject("name")
+    String name;
+
     @EndpointInject("mock:out")
     MockEndpoint mock;
 
@@ -52,4 +57,28 @@ class OverridePropertiesTest {
         mock.assertIsSatisfied();
         assertEquals("Hello John!", result);
     }
+
+    @Nested
+    @CamelMainTest(properties = "name2=Willow")
+    class NestedTest {
+
+        @PropertyInject("name2")
+        String name2;
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            assertEquals("John", name);
+            assertEquals("Willow", name2);
+        }
+
+        @Nested
+        class SuperNestedTest {
+
+            @Test
+            void shouldSupportNestedTest() throws Exception {
+                assertEquals("John", name);
+                assertEquals("Willow", name2);
+            }
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceBeanFromFieldTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceBeanFromFieldTest.java
index 881056c..b46cb8c 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceBeanFromFieldTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceBeanFromFieldTest.java
@@ -25,6 +25,7 @@ import org.apache.camel.test.main.junit5.Configure;
 import org.apache.camel.test.main.junit5.ReplaceInRegistry;
 import org.apache.camel.test.main.junit5.common.Greetings;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -62,6 +63,21 @@ class ReplaceBeanFromFieldTest {
         assertEquals("Hi Willy!", result);
     }
 
+    @Nested
+    class NestedTest {
+
+        @ReplaceInRegistry
+        Greetings myGreetings = new CustomGreetings("Willow");
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            mock.expectedBodiesReceived("Hi Willow!");
+            String result = template.requestBody((Object) null, String.class);
+            mock.assertIsSatisfied();
+            assertEquals("Hi Willow!", result);
+        }
+    }
+
     static class CustomGreetings extends Greetings {
 
         public CustomGreetings(String name) {
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceBeanFromMethodTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceBeanFromMethodTest.java
index 0048b58..12019ba 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceBeanFromMethodTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceBeanFromMethodTest.java
@@ -26,6 +26,7 @@ import org.apache.camel.test.main.junit5.Configure;
 import org.apache.camel.test.main.junit5.ReplaceInRegistry;
 import org.apache.camel.test.main.junit5.common.Greetings;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -68,6 +69,23 @@ class ReplaceBeanFromMethodTest {
         assertEquals("Hi Will!", result);
     }
 
+    @Nested
+    class NestedTest {
+
+        @ReplaceInRegistry
+        Greetings myGreetings() {
+            return new CustomGreetings("Willow");
+        }
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            mock.expectedBodiesReceived("Hi Willow!");
+            String result = template.requestBody((Object) null, String.class);
+            mock.assertIsSatisfied();
+            assertEquals("Hi Willow!", result);
+        }
+    }
+
     static class CustomGreetings extends Greetings {
 
         public CustomGreetings(String name) {
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceRouteFromTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceRouteFromTest.java
index 71f24aa..87891b7 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceRouteFromTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceRouteFromTest.java
@@ -23,6 +23,7 @@ import org.apache.camel.main.MainConfigurationProperties;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.Configure;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -30,7 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 /**
  * Test class ensuring that a from endpoint can be replaced.
  */
-@CamelMainTest(replaceRouteFromWith = { "foo", "direct:foo" })
+@CamelMainTest(replaceRouteFromWith = { "foo=direct:foo" })
 class ReplaceRouteFromTest {
 
     @EndpointInject("mock:out")
@@ -52,4 +53,20 @@ class ReplaceRouteFromTest {
         mock.assertIsSatisfied();
         assertEquals("Hello Will!", result);
     }
+
+    @CamelMainTest(replaceRouteFromWith = { "foo=direct:bar" })
+    @Nested
+    class NestedTest {
+
+        @EndpointInject("direct:bar")
+        ProducerTemplate templateBar;
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            mock.expectedBodiesReceived("Hello Will!");
+            String result = templateBar.requestBody((Object) null, String.class);
+            mock.assertIsSatisfied();
+            assertEquals("Hello Will!", result);
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/SupportParameterizedTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/SupportParameterizedTest.java
index 113b25c..17784d2 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/SupportParameterizedTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/SupportParameterizedTest.java
@@ -22,6 +22,7 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.common.MyMainClass;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
@@ -52,4 +53,14 @@ class SupportParameterizedTest {
         mock.assertIsSatisfied();
         assertEquals(value, result);
     }
+
+    @Nested
+    class NestedTest {
+
+        @ParameterizedTest
+        @ValueSource(strings = { "hello", "nested", "test" })
+        void shouldSupportNestedTest(String value) throws Exception {
+            shouldSupportMultipleCalls(value);
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/SupportRepeatedTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/SupportRepeatedTest.java
index e0fc3fc..e2b90a6 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/SupportRepeatedTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/SupportRepeatedTest.java
@@ -22,6 +22,7 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.common.MyMainClass;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.RepeatedTest;
 import org.junit.jupiter.api.RepetitionInfo;
 
@@ -52,4 +53,13 @@ class SupportRepeatedTest {
         mock.assertIsSatisfied();
         assertEquals(repetitionInfo.getCurrentRepetition(), result);
     }
+
+    @Nested
+    class NestedTest {
+
+        @RepeatedTest(5)
+        void shouldSupportNestedTest(RepetitionInfo repetitionInfo) throws Exception {
+            shouldSupportMultipleCalls(repetitionInfo);
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstanceDefaultTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstanceDefaultTest.java
index dfef562..9efb623 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstanceDefaultTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstanceDefaultTest.java
@@ -22,6 +22,7 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.common.MyMainClass;
 import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestMethodOrder;
@@ -58,4 +59,27 @@ class TestInstanceDefaultTest {
         mock.assertIsSatisfied();
         assertEquals(1, result);
     }
+
+    @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+    @Nested
+    class NestedTest {
+
+        @Order(1)
+        @Test
+        void shouldSupportNestedTestLaunchedFirst() throws Exception {
+            mock.expectedBodiesReceived(1);
+            int result = template.requestBody((Object) null, Integer.class);
+            mock.assertIsSatisfied();
+            assertEquals(1, result);
+        }
+
+        @Order(2)
+        @Test
+        void shouldSupportNestedTestLaunchedSecondWithSameResult() throws Exception {
+            mock.expectedBodiesReceived(1);
+            int result = template.requestBody((Object) null, Integer.class);
+            mock.assertIsSatisfied();
+            assertEquals(1, result);
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstancePerClassTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstancePerClassTest.java
index bbe0cbe4..5409487 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstancePerClassTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstancePerClassTest.java
@@ -23,6 +23,7 @@ import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.common.MyMainClass;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
@@ -66,4 +67,27 @@ class TestInstancePerClassTest {
         mock.assertIsSatisfied();
         assertEquals(2, result);
     }
+
+    @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+    @Nested
+    class NestedTest {
+
+        @Order(1)
+        @Test
+        void shouldSupportNestedTestLaunchedFirst() throws Exception {
+            mock.expectedBodiesReceived(3);
+            int result = template.requestBody((Object) null, Integer.class);
+            mock.assertIsSatisfied();
+            assertEquals(3, result);
+        }
+
+        @Order(2)
+        @Test
+        void shouldSupportNestedTestLaunchedSecondWithDifferentResult() throws Exception {
+            mock.expectedBodiesReceived(4);
+            int result = template.requestBody((Object) null, Integer.class);
+            mock.assertIsSatisfied();
+            assertEquals(4, result);
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstancePerMethodTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstancePerMethodTest.java
index 70a9656..bbaffe8 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstancePerMethodTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/TestInstancePerMethodTest.java
@@ -22,6 +22,7 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.common.MyMainClass;
 import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
@@ -60,4 +61,27 @@ class TestInstancePerMethodTest {
         mock.assertIsSatisfied();
         assertEquals(1, result);
     }
+
+    @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+    @Nested
+    class NestedTest {
+
+        @Order(1)
+        @Test
+        void shouldSupportNestedTestLaunchedFirst() throws Exception {
+            mock.expectedBodiesReceived(1);
+            int result = template.requestBody((Object) null, Integer.class);
+            mock.assertIsSatisfied();
+            assertEquals(1, result);
+        }
+
+        @Order(2)
+        @Test
+        void shouldSupportNestedTestLaunchedSecondWithSameResult() throws Exception {
+            mock.expectedBodiesReceived(1);
+            int result = template.requestBody((Object) null, Integer.class);
+            mock.assertIsSatisfied();
+            assertEquals(1, result);
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/UseSeveralPropertyPlaceholderLocationsTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/UseSeveralPropertyPlaceholderLocationsTest.java
index 53c6894..016e1e1 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/UseSeveralPropertyPlaceholderLocationsTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/UseSeveralPropertyPlaceholderLocationsTest.java
@@ -23,6 +23,7 @@ import org.apache.camel.main.MainConfigurationProperties;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.Configure;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -52,4 +53,17 @@ class UseSeveralPropertyPlaceholderLocationsTest {
         mock.assertIsSatisfied();
         assertEquals("Hello Jack!", result);
     }
+
+    @CamelMainTest(propertyPlaceholderLocations = { "classpath:application.properties" })
+    @Nested
+    class NestedTest {
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            mock.expectedBodiesReceived("Hello Will!");
+            String result = template.requestBody((Object) null, String.class);
+            mock.assertIsSatisfied();
+            assertEquals("Hello Will!", result);
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithConfigurationClassTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithConfigurationClassTest.java
index 4ad46ce..0f7de80 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithConfigurationClassTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithConfigurationClassTest.java
@@ -20,7 +20,9 @@ import org.apache.camel.EndpointInject;
 import org.apache.camel.ProducerTemplate;
 import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.main.junit5.CamelMainTest;
+import org.apache.camel.test.main.junit5.annotation.other.MyOtherConfiguration;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -44,4 +46,17 @@ class WithConfigurationClassTest {
         mock.assertIsSatisfied();
         assertEquals("Hello Will!", result);
     }
+
+    @CamelMainTest(configurationClasses = MyOtherConfiguration.class)
+    @Nested
+    class NestedTest {
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            mock.expectedBodiesReceived("Hello Mark!");
+            String result = template.requestBody((Object) null, String.class);
+            mock.assertIsSatisfied();
+            assertEquals("Hello Mark!", result);
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithDebuggerCallbackTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithDebuggerCallbackTest.java
index 99d417b..c46b442 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithDebuggerCallbackTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithDebuggerCallbackTest.java
@@ -28,6 +28,7 @@ import org.apache.camel.model.ProcessorDefinition;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.DebuggerCallback;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -56,6 +57,15 @@ class WithDebuggerCallbackTest implements DebuggerCallback {
         assertEquals(4, counter.get());
     }
 
+    @Nested
+    class NestedTest {
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            shouldInvokeCallbackMethods();
+        }
+    }
+
     @Override
     public void debugBefore(
             Exchange exchange, Processor processor, ProcessorDefinition<?> definition, String id, String label) {
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithMainClassTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithMainClassTest.java
index 83346e2..ab6df1d 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithMainClassTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/WithMainClassTest.java
@@ -20,7 +20,9 @@ import org.apache.camel.EndpointInject;
 import org.apache.camel.ProducerTemplate;
 import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.main.junit5.CamelMainTest;
+import org.apache.camel.test.main.junit5.annotation.other.MyOtherMainClass;
 import org.apache.camel.test.main.junit5.common.MyMainClass;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -44,4 +46,35 @@ class WithMainClassTest {
         mock.assertIsSatisfied();
         assertEquals("Hello Will!", result);
     }
+
+    @CamelMainTest(mainClass = MyOtherMainClass.class)
+    @Nested
+    class NestedTest {
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            mock.expectedBodiesReceived("Hello Mark!");
+            String result = template.requestBody((Object) null, String.class);
+            mock.assertIsSatisfied();
+            assertEquals("Hello Mark!", result);
+        }
+
+        @Nested
+        class SuperNestedTest {
+
+            @Test
+            void shouldSupportSuperNestedTest() throws Exception {
+                shouldSupportNestedTest();
+            }
+        }
+    }
+
+    @Nested
+    class OtherNestedTest {
+
+        @Test
+        void shouldSupportOtherNestedTest() throws Exception {
+            shouldFindTheRouteBuilder();
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/custom/LoadCustomConfigurationDefaultPackageTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/custom/LoadCustomConfigurationDefaultPackageTest.java
index 5c20ce7..526cc93 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/custom/LoadCustomConfigurationDefaultPackageTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/custom/LoadCustomConfigurationDefaultPackageTest.java
@@ -23,6 +23,7 @@ import org.apache.camel.main.MainConfigurationProperties;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.Configure;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -55,4 +56,17 @@ class LoadCustomConfigurationDefaultPackageTest {
         mock.assertIsSatisfied();
         assertEquals("Hello Johnny!", result);
     }
+
+    @CamelMainTest(propertyPlaceholderFileName = "application.properties")
+    @Nested
+    class NestedTest {
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            mock.expectedBodiesReceived("Hello Will!");
+            String result = template.requestBody((Object) null, String.class);
+            mock.assertIsSatisfied();
+            assertEquals("Hello Will!", result);
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/custom/LoadCustomConfigurationSamePackageTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/custom/LoadCustomConfigurationSamePackageTest.java
index 3896776..e5c0e3c3 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/custom/LoadCustomConfigurationSamePackageTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/custom/LoadCustomConfigurationSamePackageTest.java
@@ -23,6 +23,7 @@ import org.apache.camel.main.MainConfigurationProperties;
 import org.apache.camel.test.main.junit5.CamelMainTest;
 import org.apache.camel.test.main.junit5.Configure;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -55,4 +56,13 @@ class LoadCustomConfigurationSamePackageTest {
         mock.assertIsSatisfied();
         assertEquals("Hello John!", result);
     }
+
+    @Nested
+    class NestedTest {
+
+        @Test
+        void shouldSupportNestedTest() throws Exception {
+            shouldFindCustomConfiguration();
+        }
+    }
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/InheritanceTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/other/MyOtherConfiguration.java
similarity index 57%
copy from components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/InheritanceTest.java
copy to components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/other/MyOtherConfiguration.java
index 3bcf657..7d8d093 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/InheritanceTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/other/MyOtherConfiguration.java
@@ -14,22 +14,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.test.main.junit5.annotation;
+package org.apache.camel.test.main.junit5.annotation.other;
 
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.CamelConfiguration;
+import org.apache.camel.CamelContext;
+import org.apache.camel.test.main.junit5.common.Greetings;
 
 /**
- * A test class ensuring that the annotation inheritance works as expected.
+ * Class to configure the Camel application.
  */
-class InheritanceTest extends AdviceRouteTest {
+public class MyOtherConfiguration implements CamelConfiguration {
+
+    @BindToRegistry
+    public Greetings myGreetings() {
+        return new Greetings("Mark");
+    }
 
-    @Test
-    void shouldInheritTheAnnotation() throws Exception {
-        mock.expectedBodiesReceived("Hello Will!");
-        String result = template.requestBody((Object) null, String.class);
-        mock.assertIsSatisfied();
-        assertEquals("Hello Will!", result);
+    @Override
+    public void configure(CamelContext camelContext) {
+        // this method is optional and can be removed if no additional configuration is needed.
     }
+
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/InheritanceTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/other/MyOtherMainClass.java
similarity index 58%
copy from components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/InheritanceTest.java
copy to components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/other/MyOtherMainClass.java
index 3bcf657..79ba475 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/InheritanceTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/other/MyOtherMainClass.java
@@ -14,22 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.test.main.junit5.annotation;
+package org.apache.camel.test.main.junit5.annotation.other;
 
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-/**
- * A test class ensuring that the annotation inheritance works as expected.
- */
-class InheritanceTest extends AdviceRouteTest {
-
-    @Test
-    void shouldInheritTheAnnotation() throws Exception {
-        mock.expectedBodiesReceived("Hello Will!");
-        String result = template.requestBody((Object) null, String.class);
-        mock.assertIsSatisfied();
-        assertEquals("Hello Will!", result);
-    }
+public class MyOtherMainClass {
 }
diff --git a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceRouteFromTest.java b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/legacy/ConfigureTest.java
similarity index 68%
copy from components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceRouteFromTest.java
copy to components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/legacy/ConfigureTest.java
index 71f24aa..0687a36 100644
--- a/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/annotation/ReplaceRouteFromTest.java
+++ b/components/camel-test/camel-test-main-junit5/src/test/java/org/apache/camel/test/main/junit5/legacy/ConfigureTest.java
@@ -14,41 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.test.main.junit5.annotation;
+package org.apache.camel.test.main.junit5.legacy;
 
-import org.apache.camel.EndpointInject;
-import org.apache.camel.ProducerTemplate;
 import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.main.MainConfigurationProperties;
-import org.apache.camel.test.main.junit5.CamelMainTest;
-import org.apache.camel.test.main.junit5.Configure;
+import org.apache.camel.test.main.junit5.CamelMainTestSupport;
 import org.apache.camel.test.main.junit5.common.MyConfiguration;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
- * Test class ensuring that a from endpoint can be replaced.
+ * A test class ensuring that a Camel context can be configured.
  */
-@CamelMainTest(replaceRouteFromWith = { "foo", "direct:foo" })
-class ReplaceRouteFromTest {
+class ConfigureTest extends CamelMainTestSupport {
 
-    @EndpointInject("mock:out")
-    MockEndpoint mock;
-
-    @EndpointInject("direct:foo")
-    ProducerTemplate template;
-
-    @Configure
+    @Override
     protected void configure(MainConfigurationProperties configuration) {
         // Add the configuration class
         configuration.addConfiguration(MyConfiguration.class);
     }
 
     @Test
-    void shouldReplaceTheFromEndpoint() throws Exception {
+    void shouldConfigureTheCamelContext() throws Exception {
+        MockEndpoint mock = context.getEndpoint("mock:out", MockEndpoint.class);
         mock.expectedBodiesReceived("Hello Will!");
-        String result = template.requestBody((Object) null, String.class);
+        String result = template.requestBody("direct:in", null, String.class);
         mock.assertIsSatisfied();
         assertEquals("Hello Will!", result);
     }