`. More complex services may use the builder available thr
the service accordingly.
-=== Registering Properties
+==== Registering Properties
All services should register the properties, via `System.setProperty` that allow access to the services. This is required
in order to resolve those properties when running tests using the Spring framework. This registration allows the properties
@@ -74,11 +149,12 @@ to be resolved in Spring's XML files.
This registration is done in the `registerProperties` methods during the service initialization.
-=== Registering Properties Example:
+==== Registering Properties Example:
-Registering the properties in the concrete service implementation:
+Registering the properties in the concrete service implementation:
-```
+[source,java]
+----
public void registerProperties() {
// MyServiceProperties.MY_SERVICE_HOST is a string with value "my.service.host"
System.setProperty(MyServiceProperties.MY_SERVICE_HOST, container.getHost());
@@ -97,25 +173,26 @@ Registering the properties in the concrete service implementation:
registerProperties();
LOG.info("MyService instance running at {}", getServiceAddress());
}
-```
+----
Then, when referring these properties in Camel routes or Spring XML properties, you may use `{{my.service.host}}`,
`{{my.service.port}}` and `{{my.service.address}}`.
-=== Packaging Recommendations
+==== Packaging Recommendations
This is test infrastructure code, therefore it should be package as test type artifacts. The
https://github.com/apache/camel/blob/main/test-infra/camel-test-infra-parent[parent pom] should provide all the necessary bits for packaging the test infrastructure.
-== Using The New Test Infrastructure
+=== Using The New Test Infrastructure
-Using the test infra in a new component test is rather straightforward similar to using any other reusable component.
+Using the test infra in a new component test is rather straightforward, similar to using any other reusable component.
You start by declaring the test infra dependencies in your pom file.
This should be similar to:
-```xml
+[source,xml]
+----
org.apache.camel
@@ -124,22 +201,27 @@ This should be similar to:
test-jar
test
-```
+----
-*Note*: on the dependencies above, the dependency version is set to `${project.version}`. This should be adjusted to the
+[NOTE]
+====
+On the dependencies above, the dependency version is set to `${project.version}`. This should be adjusted to the
Camel version when used outside the Camel Core project.
+====
On the test class, add a member variable for the service and annotate it with the https://junit.org/junit5/docs/5.1.1/api/org/junit/jupiter/api/extension/RegisterExtension.html[@RegisterExtension],
-in order to let JUnit 5 manage its lifecycle.
+in order to let JUnit 5 manage its lifecycle.
-```
+[source,java]
+----
@RegisterExtension
static MyService service = MyServiceServiceFactory.createService();
-```
+----
-More complex test services can be created using something similar to:
+More complex test services can be created using something similar to:
-```
+[source,java]
+----
@RegisterExtension
static MyService service = MyServiceServiceFactory
.builder()
@@ -147,19 +229,21 @@ static MyService service = MyServiceServiceFactory
.addLocalMapping(MyTestClass::staticMethodReturningAService) // sets the handler for -Dmy-service.instance.type=local-myservice-local-container
.addMapping("local-alternative-service", MyTestClass::anotherMethodReturningAService) // sets the handler for -Dmy-service.instance.type=local-alternative-service
.createService();
-```
+----
You can use the methods as well as the registered properties to access the test infrastructure services available.
-When using these properties in Spring XML files, you may use those properties.
+When using these properties in Spring XML files, you may use those properties.
-```
+[source,xml]
+----
-```
+----
It's also possible to use these properties in the test code itself. For example, when setting up the test url for the
Camel component:
-```
+[source,java]
+----
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
public void configure() {
@@ -168,11 +252,34 @@ Camel component:
}
};
}
-```
+----
+
+==== Execution Ordering
+
+When combining the different modules of the test infra, you may need to ensure that they execute in the proper order. You can do so by using JUnit's `@Order` annotation.
+
+For instance:
+
+[source,java]
+----
+ @Order(1)
+ @RegisterExtension
+ protected static KafkaService service = KafkaServiceFactory.createSingletonService();
+
+ @Order(2)
+ @RegisterExtension
+ protected static CamelContextExtension contextExtension = new DefaultCamelContextExtension();
+----
== Converting Camel TestContainers Code To The New Test Infrastructure
+
+[NOTE]
+====
+This section is aimed at Camel maintainers that need to write new test infra components. End users can skip this section.
+====
+
Using the camel-nats as an example, we can compare how the base test class for nats changed between https://github.com/apache/camel/blob/camel-3.6.0/components/camel-nats/src/test/java/org/apache/camel/component/nats/NatsTestSupport.java[3.6.x]
and https://github.com/apache/camel/blob/camel-3.7.0/components/camel-nats/src/test/java/org/apache/camel/component/nats/NatsTestSupport.java[3.7.x].
diff --git a/docs/user-manual/modules/ROOT/pages/testing.adoc b/docs/user-manual/modules/ROOT/pages/testing.adoc
index c126571cad993..10cec2e6b7712 100644
--- a/docs/user-manual/modules/ROOT/pages/testing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/testing.adoc
@@ -20,14 +20,14 @@ The following modules are supported:
|=======================================================================
|Component |Description
-|xref:components:others:test-junit5.adoc[camel-test-junit5] |*JUnit 5*: Is a standalone Java
+|xref:components:others:test-junit5.adoc[camel-test-junit5] |*JUnit 5*: Is an older standalone Java
library letting you easily create Camel test cases using a single Java
class for all your configuration and routing without.
|xref:components:others:test-main-junit5.adoc[camel-test-main-junit5] | *JUnit 5*: Used for testing Camel in Camel Main mode
|xref:components:others:test-spring-junit5.adoc[camel-test-spring-junit5] | *JUnit 5*: Used for testing Camel with Spring / Spring Boot
-|xref:test-infra.adoc[camel-test-infra] | *Camel Test Infra*: Camel Test Infra is a set of modules that leverage Test Containers and abstract the provisioning and execution of test infrastructure. It is the successor of the camel-testcontainers components.
+|xref:test-infra.adoc[camel-test-infra] | *Camel Test Infra*: Camel Test Infra is a set of modules that leverage modern JUnit 5 features to abstract the provisioning and execution of test infrastructure. Among other things, it provides abstraction of the infrastructure (based on Test Containers - being the de-facto successor of the camel-testcontainers components) as well as JUnit 5 extensions for the Camel Context itself.
|=======================================================================
diff --git a/test-infra/camel-test-infra-core/pom.xml b/test-infra/camel-test-infra-core/pom.xml
new file mode 100644
index 0000000000000..4ffa7507ec2a3
--- /dev/null
+++ b/test-infra/camel-test-infra-core/pom.xml
@@ -0,0 +1,80 @@
+
+
+
+
+ 4.0.0
+
+ org.apache.camel
+ test-infra
+ 4.0.0-SNAPSHOT
+
+
+ camel-test-infra-core
+ jar
+ Camel :: Test Infra :: Core
+ Core testing infrastructure for Camel components
+
+
+ 4.0.0
+
+
+
+
+
+ org.apache.camel
+ camel-core
+ test
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ test-compile
+
+ test-jar
+
+
+
+
+
+
+
diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/CamelContextExtension.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/CamelContextExtension.java
new file mode 100644
index 0000000000000..1ddceac2b9868
--- /dev/null
+++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/CamelContextExtension.java
@@ -0,0 +1,68 @@
+/*
+ * 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.test.infra.core;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ConsumerTemplate;
+import org.apache.camel.NoSuchEndpointException;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+
+/**
+ * A JUnit 5 extension that allows you to include a {@link CamelContext} in your test code.
+ */
+public interface CamelContextExtension extends BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
+
+ /**
+ * Gets the {@link CamelContext} created by this extension
+ * @return an instance of the Camel context to use in the test
+ */
+ CamelContext getContext();
+
+ /**
+ * Creates a {@link ProducerTemplate} from the context
+ * @return an instance of a producer template to use with the test
+ */
+ ProducerTemplate getProducerTemplate();
+
+ /**
+ * Creates a {@link ConsumerTemplate} from the context
+ * @return an instance of a consumer template to use with the test
+ */
+ ConsumerTemplate getConsumerTemplate();
+
+ /**
+ * Gets a {@link MockEndpoint} for the given URI. If the endpoint does not exist, it will be created
+ * @param uri the URI to create the mock to
+ * @return a Mock endpoint instance for the given URI
+ */
+ MockEndpoint getMockEndpoint(String uri);
+
+ /**
+ * Gets a {@link MockEndpoint} for the given URI.
+ * @param uri the URI to create the mock to
+ * @param create whether to create the endpoint if it does not exist
+ * @return a Mock endpoint instance for the given URI
+ * @throws NoSuchEndpointException if the endpoint does not exist and a new one should not be created
+ */
+ MockEndpoint getMockEndpoint(String uri, boolean create) throws NoSuchEndpointException;
+}
diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextFixture.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextFixture.java
new file mode 100644
index 0000000000000..b92f713652592
--- /dev/null
+++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextFixture.java
@@ -0,0 +1,35 @@
+/*
+ * 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.test.infra.core;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation indicates the need for configuring Camel contexts. Use it to annotate methods that configure the context.
+ *
+ * The signature for such methods should be: public void configureContext(CamelContext context).
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface ContextFixture {
+}
diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextLifeCycleManager.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextLifeCycleManager.java
new file mode 100644
index 0000000000000..12805a015a73f
--- /dev/null
+++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextLifeCycleManager.java
@@ -0,0 +1,51 @@
+/*
+ * 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.test.infra.core;
+
+import org.apache.camel.CamelContext;
+
+/**
+ * A life cycle manager for the Camel instance manages how the context is started, stopped and configured based on which
+ * phase of the test is being executed.
+ */
+public interface ContextLifeCycleManager {
+
+ /**
+ * A hook to execute after all tests are executed
+ * @param context the context instance
+ */
+ void afterAll(CamelContext context);
+
+ /**
+ * A hook to execute before all tests are executed
+ * @param context the context instance
+ */
+ void beforeAll(CamelContext context);
+
+ /**
+ * A hook to execute after each test is executed
+ * @param context the context instance
+ */
+ void afterEach(CamelContext context);
+
+ /**
+ * A hook to execute before each test is executed
+ * @param context the context instance
+ */
+ void beforeEach(CamelContext context);
+}
diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultCamelContextExtension.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultCamelContextExtension.java
new file mode 100644
index 0000000000000..7063ab210b135
--- /dev/null
+++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultCamelContextExtension.java
@@ -0,0 +1,282 @@
+/*
+ * 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.test.infra.core;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.CamelContext;
+import org.apache.camel.ConsumerTemplate;
+import org.apache.camel.Endpoint;
+import org.apache.camel.EndpointInject;
+import org.apache.camel.NoSuchEndpointException;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.util.URISupport;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * A simple Camel context extension suitable for most of the simple use cases in Camel and end-user applications.
+ */
+public class DefaultCamelContextExtension implements CamelContextExtension {
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultCamelContextExtension.class);
+ private final ContextLifeCycleManager lifeCycleManager;
+ private CamelContext context;
+ private ProducerTemplate producerTemplate;
+ private ConsumerTemplate consumerTemplate;
+
+ /**
+ * Creates a new instance of the extension
+ */
+ public DefaultCamelContextExtension() {
+ this(new DefaultContextLifeCycleManager());
+ }
+
+ /**
+ * Creates a new instance of the extension with a custom {@link ContextLifeCycleManager}
+ *
+ * @param lifeCycleManager a life cycle manager for the context
+ */
+ public DefaultCamelContextExtension(ContextLifeCycleManager lifeCycleManager) {
+ this.lifeCycleManager = lifeCycleManager;
+ }
+
+ /**
+ * Resolves an endpoint and asserts that it is found.
+ */
+ public static T resolveMandatoryEndpoint(CamelContext context, String endpointUri, Class endpointType) {
+ T endpoint = context.getEndpoint(endpointUri, endpointType);
+
+ assertNotNull(endpoint, "No endpoint found for URI: " + endpointUri);
+
+ return endpoint;
+ }
+
+ private static String commonFixtureMessage(Class extends Annotation> annotationClass, Object instance) {
+ return "Unable to setup fixture " + annotationClass.getSimpleName() + " on " + instance.getClass().getName();
+ }
+
+ private static String invocationTargetMessage(Class extends Annotation> annotationClass, Object instance, String methodName) {
+ return commonFixtureMessage(annotationClass, instance) + " due to invocation target exception to method: " + methodName;
+ }
+
+ private static String illegalAccessMessage(Class extends Annotation> annotationClass, Object instance, String methodName) {
+ return commonFixtureMessage(annotationClass, instance) + " due to illegal access to method: " + methodName;
+ }
+
+ protected CamelContext createCamelContext() {
+ return new DefaultCamelContext();
+ }
+
+ @Override
+ public void afterAll(ExtensionContext extensionContext) throws Exception {
+ lifeCycleManager.afterAll(context);
+ }
+
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) throws Exception {
+ context = createCamelContext();
+
+ producerTemplate = context.createProducerTemplate();
+ producerTemplate.start();
+
+ consumerTemplate = context.createConsumerTemplate();
+ consumerTemplate.start();
+
+ lifeCycleManager.beforeAll(context);
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) throws Exception {
+ final Object o = extensionContext.getTestInstance().get();
+
+ LOG.info("********************************************************************************");
+ LOG.info("Testing: {} ({})", extensionContext.getDisplayName(), o.getClass().getName());
+
+ setupFixtureFields(extensionContext, EndpointInject.class, o);
+ setupFixtureFields(extensionContext, BindToRegistry.class, o);
+
+ if (!context.isStarted()) {
+ setupFixture(extensionContext, ContextFixture.class, o);
+ setupFixture(extensionContext, RouteFixture.class, o);
+
+ lifeCycleManager.beforeEach(context);
+ }
+ }
+
+ @Override
+ public void afterEach(ExtensionContext extensionContext) throws Exception {
+ lifeCycleManager.afterEach(context);
+
+ final Object o = extensionContext.getTestInstance().get();
+ LOG.info("Testing done: {} ({})", extensionContext.getDisplayName(), o.getClass().getName());
+ LOG.info("********************************************************************************");
+ }
+
+ private void setupFixture(ExtensionContext extensionContext, Class extends Annotation> annotationClass, Object instance) {
+ final Class> testClass = extensionContext.getTestClass().get();
+
+ Arrays.stream(testClass.getMethods()).filter(m -> m.isAnnotationPresent(annotationClass)).forEach(m -> doInvokeFixture(annotationClass, instance, m));
+ }
+
+ private void setupFixtureFields(ExtensionContext extensionContext, Class extends Annotation> annotationClass, Object instance) {
+ final Class> testClass = extensionContext.getTestClass().get();
+
+ var superClass = testClass.getSuperclass();
+ while (superClass != null) {
+ Arrays.stream(superClass.getDeclaredFields()).filter(m -> m.isAnnotationPresent(annotationClass)).forEach(f -> doInvokeFixture(f.getAnnotation(annotationClass), instance, f));
+
+ superClass = superClass.getSuperclass();
+ }
+
+ Arrays.stream(testClass.getDeclaredFields()).filter(f -> f.isAnnotationPresent(annotationClass)).forEach(f -> doInvokeFixture(f.getAnnotation(annotationClass), instance, f));
+ }
+
+ private void doInvokeFixture(Class extends Annotation> annotationClass, Object instance, Method m) {
+ var methodName = m.getName();
+ LOG.trace("Checking instance method: {}", methodName);
+ try {
+ m.invoke(instance, context);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(illegalAccessMessage(annotationClass, instance, methodName), e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(invocationTargetMessage(annotationClass, instance, methodName), e);
+ }
+ }
+
+ private void doInvokeFixture(Annotation annotation, Object instance, Field f) {
+ var fieldName = f.getName();
+ LOG.trace("Checking instance field: {}", fieldName);
+ try {
+ if (!Modifier.isPublic(f.getModifiers())) {
+ f.setAccessible(true);
+ }
+
+ if (annotation instanceof BindToRegistry r) {
+ String bindValue = r.value();
+
+ context.getRegistry().bind(bindValue, f.get(instance));
+
+ return;
+ }
+
+ if (annotation instanceof EndpointInject e) {
+ String uri = e.value();
+
+ if (f.getType() == MockEndpoint.class) {
+ final MockEndpoint mockEndpoint = getMockEndpoint(uri);
+
+ assert mockEndpoint != null;
+
+ f.set(instance, mockEndpoint);
+ } else {
+ final Endpoint endpoint = context.getEndpoint(uri);
+
+ assert endpoint != null;
+ f.set(instance, endpoint);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(illegalAccessMessage(annotation.getClass(), instance, fieldName), e);
+ }
+ }
+
+ @Override
+ public CamelContext getContext() {
+ return context;
+ }
+
+ @Override
+ public ProducerTemplate getProducerTemplate() {
+ return producerTemplate;
+ }
+
+ @Override
+ public ConsumerTemplate getConsumerTemplate() {
+ return consumerTemplate;
+ }
+
+ /**
+ * Resolves a mandatory endpoint for the given URI and expected type or an exception is thrown
+ *
+ * @param uri the Camel URI to use to create or resolve an endpoint
+ * @param endpointType the endpoint type (i.e., its class) to resolve
+ * @return the endpoint
+ */
+ protected T resolveMandatoryEndpoint(String uri, Class endpointType) {
+ return resolveMandatoryEndpoint(context, uri, endpointType);
+ }
+
+ @Override
+ public MockEndpoint getMockEndpoint(String uri) {
+ MockEndpoint mock = getMockEndpoint(uri, true);
+
+ return mock;
+ }
+
+ @Override
+ public MockEndpoint getMockEndpoint(String uri, boolean create) throws NoSuchEndpointException {
+ // look for existing mock endpoints that have the same queue name, and
+ // to
+ // do that we need to normalize uri and strip out query parameters and
+ // whatnot
+ String n;
+ try {
+ n = URISupport.normalizeUri(uri);
+ } catch (Exception e) {
+ throw RuntimeCamelException.wrapRuntimeException(e);
+ }
+ // strip query
+ int idx = n.indexOf('?');
+ if (idx != -1) {
+ n = n.substring(0, idx);
+ }
+ final String target = n;
+
+ // lookup endpoints in registry and try to find it
+ MockEndpoint found = (MockEndpoint) context.getEndpointRegistry().values().stream().filter(e -> e instanceof MockEndpoint).filter(e -> {
+ String t = e.getEndpointUri();
+ // strip query
+ int idx2 = t.indexOf('?');
+ if (idx2 != -1) {
+ t = t.substring(0, idx2);
+ }
+ return t.equals(target);
+ }).findFirst().orElse(null);
+
+ if (found != null) {
+ return found;
+ }
+
+ if (create) {
+ return resolveMandatoryEndpoint(uri, MockEndpoint.class);
+ } else {
+ throw new NoSuchEndpointException(String.format("MockEndpoint %s does not exist.", uri));
+ }
+ }
+}
diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultContextLifeCycleManager.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultContextLifeCycleManager.java
new file mode 100644
index 0000000000000..7c395a473ea35
--- /dev/null
+++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultContextLifeCycleManager.java
@@ -0,0 +1,68 @@
+/*
+ * 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.test.infra.core;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.mock.MockEndpoint;
+
+/**
+ * A default lifecycle manager suitable for most of the tests within Camel and end-user applications
+ */
+public class DefaultContextLifeCycleManager implements ContextLifeCycleManager {
+ public static final int DEFAULT_SHUTDOWN_TIMEOUT = 10;
+ private int shutdownTimeout = DEFAULT_SHUTDOWN_TIMEOUT;
+ private boolean reset = true;
+
+ /**
+ * Creates a new instance of this class
+ */
+ public DefaultContextLifeCycleManager() {
+ }
+
+ /**
+ * Creates a new instance of this class
+ * @param shutdownTimeout the shutdown timeout
+ * @param reset whether to reset any {@link MockEndpoint} after each test execution
+ */
+ public DefaultContextLifeCycleManager(int shutdownTimeout, boolean reset) {
+ this.shutdownTimeout = shutdownTimeout;
+ this.reset = reset;
+ }
+
+ @Override
+ public void afterAll(CamelContext context) {
+ context.shutdown();
+ }
+
+ @Override
+ public void beforeAll(CamelContext context) {
+ context.getShutdownStrategy().setTimeout(shutdownTimeout);
+ }
+
+ @Override
+ public void afterEach(CamelContext context) {
+ if (reset) {
+ MockEndpoint.resetMocks(context);
+ }
+ }
+
+ @Override
+ public void beforeEach(CamelContext context) {
+ context.start();
+ }
+}
diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/RouteFixture.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/RouteFixture.java
new file mode 100644
index 0000000000000..a37cad638b748
--- /dev/null
+++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/RouteFixture.java
@@ -0,0 +1,35 @@
+/*
+ * 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.test.infra.core;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation indicates the need for configuring Camel routes to a context. Use it to annotate methods that configure the routes.
+ *
+ * The signature for such methods should be: public void configureRoute(CamelContext context).
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface RouteFixture {
+}
diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableContext.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableContext.java
new file mode 100644
index 0000000000000..b837f62ed861f
--- /dev/null
+++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableContext.java
@@ -0,0 +1,35 @@
+/*
+ * 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.test.infra.core.api;
+
+import org.apache.camel.CamelContext;
+
+/**
+ * A utility interface to indicate classes that configure the context. Usage of this interface is optional, but
+ * recommended for consistency.
+ */
+public interface ConfigurableContext {
+
+ /**
+ * Configures the context
+ * @param context the context to configure
+ * @throws Exception if the context cannot be configured
+ */
+ void configureContext(CamelContext context) throws Exception;
+
+}
diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableRoute.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableRoute.java
new file mode 100644
index 0000000000000..2530675204190
--- /dev/null
+++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableRoute.java
@@ -0,0 +1,34 @@
+/*
+ * 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.test.infra.core.api;
+
+import org.apache.camel.CamelContext;
+
+/**
+ * A utility interface to indicate classes that configure the routes used by the context. Usage of this interface is optional, but
+ * recommended for consistency.
+ */
+public interface ConfigurableRoute {
+
+ /**
+ * Creates the route builder and add it to the context
+ * @param context the context to configure
+ * @throws Exception if the context cannot be configured
+ */
+ void createRouteBuilder(CamelContext context) throws Exception;
+}
diff --git a/test-infra/camel-test-infra-kafka/src/test/java/org/apache/camel/test/infra/kafka/services/KafkaService.java b/test-infra/camel-test-infra-kafka/src/test/java/org/apache/camel/test/infra/kafka/services/KafkaService.java
index 92c376546dffc..57baa6311c853 100644
--- a/test-infra/camel-test-infra-kafka/src/test/java/org/apache/camel/test/infra/kafka/services/KafkaService.java
+++ b/test-infra/camel-test-infra-kafka/src/test/java/org/apache/camel/test/infra/kafka/services/KafkaService.java
@@ -23,6 +23,8 @@
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Provides an interface for any type of Kafka service: remote instances, local container, etc
@@ -39,7 +41,17 @@ public interface KafkaService
@Override
default void beforeAll(ExtensionContext extensionContext) throws Exception {
- initialize();
+ try {
+ initialize();
+ } catch (Exception e) {
+ Logger log = LoggerFactory.getLogger(KafkaService.class);
+
+ final Object o = extensionContext.getTestInstance().get();
+ log.error("Failed to initialize service {} for test {} on ({})", this.getClass().getSimpleName(),
+ extensionContext.getDisplayName(), o.getClass().getName());
+
+ throw e;
+ }
}
@Override
diff --git a/test-infra/pom.xml b/test-infra/pom.xml
index 84331f994815b..21b6f2ca2c2e6 100644
--- a/test-infra/pom.xml
+++ b/test-infra/pom.xml
@@ -75,5 +75,6 @@
camel-test-infra-hashicorp-vault
camel-test-infra-jetty
camel-test-infra-etcd3
+ camel-test-infra-core