All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.quarkus.test.CamelQuarkusTestSupport Maven / Gradle / Ivy

There is a newer version: 3.15.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.quarkus.test;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import io.quarkus.test.junit.QuarkusTestProfile;
import io.quarkus.test.junit.callback.QuarkusTestContext;
import io.quarkus.test.junit.callback.QuarkusTestMethodContext;
import jakarta.inject.Inject;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.NoSuchEndpointException;
import org.apache.camel.Processor;
import org.apache.camel.Route;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.model.ModelCamelContext;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.quarkus.core.FastCamelContext;
import org.apache.camel.spi.Registry;
import org.apache.camel.test.junit5.AbstractTestSupport;
import org.apache.camel.test.junit5.CamelContextManager;
import org.apache.camel.test.junit5.CamelTestSupport;
import org.apache.camel.test.junit5.ContextManagerFactory;
import org.apache.camel.test.junit5.TestSupport;
import org.apache.camel.test.junit5.util.ExtensionHelper;
import org.apache.camel.test.junit5.util.RouteCoverageDumperExtension;
import org.apache.camel.util.StopWatch;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The {@link CamelTestSupport} class does not work on Quarkus. This class provides a replacement, which can be used in
 * JVM mode. Note that {@link CamelQuarkusTestSupport} DOES NOT work for native mode tests.
 * 

* There are several differences between {@link CamelTestSupport} and this class. *

*
    *
  • Starting and stopping {@link CamelContext} in Camel Quarkus is generally bound to starting and stopping the * application * and this holds also when testing.
  • *
  • Starting and stopping the application under test (and thus also {@link CamelContext} is under full control of * Quarkus * JUnit Extension. It prefers keeping the application up and running unless it is told to do otherwise.
  • *
  • Hence normally the application under test is started only once for all test classes of the given Maven/Gradle * module.
  • *
  • To force Quarkus JUnit Extension to restart the application (and thus also `CamelContext`) for a given test * class, you need to assign a unique {@code @io.quarkus.test.junit.TestProfile} to that class. Check the * Quarkus documentation for * how you can do * that. (Note that * WithTestResource * has a similar effect.)
  • *
  • Camel Quarkus executes the production of beans during the build phase. Because all the tests are * build together, exclusion behavior is implemented into {@link CamelQuarkusTestSupport}. If a producer of the specific * type * and name is used in one tests, the instance will be the same for the rest of the tests.
  • *
  • JUnit Jupiter callbacks {@code BeforeEachCallback}, {@code AfterEachCallback}, {@code AfterAllCallback}, * {@code BeforeAllCallback}, * {@code BeforeTestExecutionCallback} and {@code AfterTestExecutionCallback}) might not work correctly. See the * documentation. * Methods {@code afterAll}, {@code afterEach}, {@code afterTestExecution}, {@code beforeAll} and {@code beforeEach} are * not executed anymore. * You should use {@code doAfterAll}, {@code doAfterConstruct}, {@code doAfterEach}, {@code doBeforeEach} and * {@code doBeforeAll} instead of * them.
  • *
*/ public class CamelQuarkusTestSupport extends AbstractTestSupport implements QuarkusTestProfile { private static final Logger LOG = LoggerFactory.getLogger(CamelQuarkusTestSupport.class); // @RegisterExtension // protected CamelTestSupport camelTestSupportExtension = this; private final StopWatch watch = new StopWatch(); private String currentTestName; private CamelContextManager contextManager; private final ContextManagerFactory contextManagerFactory; @Inject protected CamelContext context; private Set createdRoutes; public CamelQuarkusTestSupport() { super(new CustomTestExecutionConfiguration(), new CustomCamelContextConfiguration()); this.contextManagerFactory = new ContextNotStoppingManagerFactory(); testConfigurationBuilder() .withCustomUseAdviceWith(isUseAdviceWith()) .withJMX(useJmx()) .withUseRouteBuilder(isUseRouteBuilder()) .withDumpRouteCoverage(isDumpRouteCoverage()) .withAutoStartContext(false); contextConfiguration() .withCustomCamelContextSupplier(this::camelContextSupplier) .withCustomPostProcessor(this::postProcessTest) .withCustomRoutesSupplier(this::createRouteBuilders) .withRegistryBinder(this::bindToRegistry) .withUseOverridePropertiesWithPropertiesComponent(useOverridePropertiesWithPropertiesComponent()) .withRouteFilterExcludePattern(getRouteFilterExcludePattern()) .withRouteFilterIncludePattern(getRouteFilterIncludePattern()) .withMockEndpoints(isMockEndpoints()) .withMockEndpointsAndSkip(isMockEndpointsAndSkip()); //CQ starts and stops context with the application start/stop testConfiguration().withAutoStartContext(false); } CustomCamelContextConfiguration contextConfiguration() { return (CustomCamelContextConfiguration) camelContextConfiguration; } CustomTestExecutionConfiguration testConfigurationBuilder() { return (CustomTestExecutionConfiguration) testConfigurationBuilder; } //------------------------ quarkus callbacks --------------- /** * Replacement of {@link #afterAll(ExtensionContext)} called from {@link AfterAllCallback#afterAll(QuarkusTestContext)} */ protected void doAfterAll(QuarkusTestContext context) throws Exception { // Noop } /** * Replacement of {@link #afterEach(ExtensionContext)} called from * {@link AfterEachCallback#afterEach(QuarkusTestMethodContext)} */ protected void doAfterEach(QuarkusTestMethodContext context) throws Exception { // Noop } /** * Replacement of {@link #beforeAll(ExtensionContext)} called from {@link AfterConstructCallback#afterConstruct(Object)} * Execution differs in case of @TestInstance(TestInstance.Lifecycle.PER_METHOD). in which case a callback is * invoked * before each test (instead of {@link #beforeAll(ExtensionContext)}). */ protected void doAfterConstruct() throws Exception { // Noop } /** * Replacement of {@link #beforeEach(ExtensionContext)} called from * {@link BeforeEachCallback#beforeEach(QuarkusTestMethodContext)} */ protected void doBeforeEach(QuarkusTestMethodContext context) throws Exception { // Noop } /** * Feel free to override this method for the sake of customizing the instance returned by this implementation. * Do not create your own CamelContext instance, because there needs to exist just a single instance owned by * Quarkus CDI container. There are checks in place that will make your tests fail if you do otherwise. * * @return The context from Quarkus CDI container * @throws Exception Overridden method has to throw the same Exception as superclass. */ protected CamelContext camelContextSupplier() throws Exception { return this.context; } /** * The same functionality as {@link CamelTestSupport#bindToRegistry(Registry)}. */ protected void bindToRegistry(Registry registry) throws Exception { assertTestClassCamelContextMatchesAppCamelContext(); } /** * The same functionality as {@link CamelTestSupport#postProcessTest()} . */ protected void postProcessTest() throws Exception { assertTestClassCamelContextMatchesAppCamelContext(); template = contextManager.template(); fluentTemplate = contextManager.fluentTemplate(); consumer = contextManager.consumer(); } /** * The same functionality as {@link CamelTestSupport#context()} . */ @Override public CamelContext context() { return this.context; } @Deprecated(since = "4.7.0") public long timeTaken() { return watch.taken(); } /** * Gets the name of the current test being executed. */ public final String getCurrentTestName() { return currentTestName; } /** * Common test setup. For internal use. * * @deprecated Use {@link #setupResources()} instead * @throws Exception if unable to setup the test */ @Deprecated(since = "4.7.0") public void setUp() throws Exception { ExtensionHelper.testStartHeader(getClass(), currentTestName); setupResources(); doPreSetup(); contextManager.createCamelContext(this); context = contextManager.context(); // Log a warning in case that at least one RouteBuilder is present in the Camel registry. // It might mean, that unintentionally routes are shared across tests, or that a RouteBuilder is // created with @Produces if (isUseRouteBuilder() && !context.getRegistry().findByType(RouteBuilder.class).isEmpty()) { LOG.warn("isUseRouteBuilder = true and RouteBuilder beans are present in the Camel registry.\n" + "All tests will share their routes. If this is not desired, define your test routes " + "by overriding CamelQuarkusTestSupport.createRouteBuilder().\nOr use configuration properties " + "quarkus.camel.routes-discovery.exclude-patterns or quarkus.camel.routes-discovery.include-patterns " + "to control which routes are started."); } doPostSetup(); // only start timing after all the setup watch.restart(); } /** * Common test tear down. For internal use. * * @deprecated Use {@link #cleanupResources()} instead * @throws Exception if unable to setup the test */ @Deprecated(since = "4.7.0") public void tearDown() throws Exception { long time = watch.taken(); if (isRouteCoverageEnabled()) { ExtensionHelper.testEndFooter(getClass(), currentTestName, time, new RouteCoverageDumperExtension((ModelCamelContext) context)); } else { ExtensionHelper.testEndFooter(getClass(), currentTestName, time); } if (testConfigurationBuilder.isCreateCamelContextPerClass()) { // will tear down test specially in afterAll callback return; } LOG.debug("tearDown()"); contextManager.stop(); doPostTearDown(); cleanupResources(); } /** * Strategy to perform any post-action, after {@link CamelContext} is stopped. This is meant for internal Camel * usage and should not be used by user classes. * * @deprecated use {@link #cleanupResources()} instead. */ @Deprecated(since = "4.7.0") protected void doPostTearDown() throws Exception { // noop } /** * Factory method which derived classes can use to create a {@link RouteBuilder} to define the routes for testing */ protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @Override public void configure() { // no routes added by default } }; } /** * Factory method which derived classes can use to create an array of {@link org.apache.camel.builder.RouteBuilder}s * to define the routes for testing * * @see #createRouteBuilder() * @deprecated This method will be made private. Do not use */ @Deprecated(since = "4.7.0") protected RoutesBuilder[] createRouteBuilders() throws Exception { return new RoutesBuilder[] { createRouteBuilder() }; } void internalAfterAll(QuarkusTestContext context, ExtensionContext extensionContext) { try { if (testConfiguration().isCreateCamelContextPerClass()) { //call all clear and release methods, stop is not called as it is disabled on the camelContextManagers contextManager.stop(); } else { doPostTearDown(); } cleanupResources(); } catch (Exception e) { // ignore } } void internalBeforeAll(ExtensionContext context) { final boolean perClassPresent = context.getTestInstanceLifecycle() .filter(lc -> lc.equals(TestInstance.Lifecycle.PER_CLASS)).isPresent(); if (perClassPresent) { LOG.trace("Creating a legacy context manager for {}", context.getDisplayName()); testConfigurationBuilder().withCustomCreateCamelContextPerClass(perClassPresent); contextManager = contextManagerFactory.createContextManager(ContextManagerFactory.Type.BEFORE_ALL, testConfigurationBuilder, camelContextConfiguration); } ExtensionContext.Store globalStore = context.getStore(ExtensionContext.Namespace.GLOBAL); contextManager.setGlobalStore(globalStore); } void internalBeforeEach(ExtensionContext context) throws Exception { if (contextManager == null) { LOG.trace("Creating a transient context manager for {}", context.getDisplayName()); contextManager = contextManagerFactory.createContextManager(ContextManagerFactory.Type.BEFORE_EACH, testConfigurationBuilder, camelContextConfiguration); } currentTestName = context.getDisplayName(); ExtensionContext.Store globalStore = context.getStore(ExtensionContext.Namespace.GLOBAL); contextManager.setGlobalStore(globalStore); } /** * Strategy to perform any pre setup, before the {@link CamelContext} is created. *

* Be aware that difference in lifecycle with Quarkus may require special behavior. * If this method is overridden, super.doPreSetup() must be called. *

*/ protected void doPreSetup() throws Exception { if (isUseAdviceWith() || isUseDebugger()) { ((FastCamelContext) context).suspend(); } if (isUseRouteBuilder()) { // Save the routeIds of routes existing before setup createdRoutes = context.getRoutes() .stream() .map(Route::getRouteId) .collect(Collectors.toSet()); } } /** * Strategy to perform any post setup after the {@link CamelContext} is created. *

* Be aware that difference in lifecycle with Quarkus may require special behavior. * If this method is overridden, super.doPostSetup() must be called. *

*/ protected void doPostSetup() throws Exception { if (isUseAdviceWith() || isUseDebugger()) { ((FastCamelContext) context).resume(); if (isUseDebugger()) { ModelCamelContext mcc = (ModelCamelContext) context; List rdfs = mcc.getRouteDefinitions(); // If the context was suspended, routes were not added because it would trigger start of the context // therefore, add the routes here mcc.addRouteDefinitions(rdfs); } } if (isUseRouteBuilder()) { // Remove from the routes all routes which existed before setup Set allRoutes = context.getRoutes() .stream() .map(Route::getRouteId) .collect(Collectors.toSet()); if (createdRoutes != null) { allRoutes.removeAll(createdRoutes); } createdRoutes = allRoutes; } } /** * Override when using advice with and return true. *

* Important: You must execute method {@link #startRouteDefinitions()}} manually from the unit test * after you are done doing all the advice with. * * @return true to apply advice to existing route(s). false to disable advice. */ @Override public boolean isUseAdviceWith() { return false; } /** * Helper method to start routeDefinitions (to be used with `adviceWith`). */ protected void startRouteDefinitions() throws Exception { ModelCamelContext modelCamelContext = (ModelCamelContext) context; List definitions = new ArrayList<>(modelCamelContext.getRouteDefinitions()); for (Route r : context.getRoutes()) { //existing route does not need to be started definitions.remove(r.getRoute()); } modelCamelContext.startRouteDefinitions(definitions); } /** * Resolves the mandatory Mock endpoint using a URI of the form mock:someName * * @param uri the URI which typically starts with "mock:" and has some name * @return the mandatory mock endpoint or an exception is thrown if it could not be resolved */ protected final MockEndpoint getMockEndpoint(String uri) { return getMockEndpoint(uri, true); } /** * Resolves the {@link MockEndpoint} using a URI of the form mock:someName, optionally creating it if * it does not exist. This implementation will lookup existing mock endpoints and match on the mock queue name, eg * mock:foo and mock:foo?retainFirst=5 would match as the queue name is foo. * * @param uri the URI which typically starts with "mock:" and has some name * @param create whether to allow the endpoint to be created if it doesn't exist * @return the mock endpoint or an {@link NoSuchEndpointException} is thrown if it could not * be resolved * @throws NoSuchEndpointException is the mock endpoint does not exist */ @Deprecated(since = "4.7.0") protected final MockEndpoint getMockEndpoint(String uri, boolean create) throws NoSuchEndpointException { return TestSupport.getMockEndpoint(context, uri, create); } /** * Single step debugs and Camel invokes this method before entering the given processor. This method is NOOP. * * @deprecated Use {@link #camelContextConfiguration()} to set an instance of {@link DebugBreakpoint} */ @Deprecated(since = "4.7.0") protected void debugBefore( Exchange exchange, Processor processor, ProcessorDefinition definition, String id, String label) { } /** * Single step debugs and Camel invokes this method after processing the given processor. This method is NOOP. * * @deprecated Use {@link #camelContextConfiguration()} to set an instance of {@link DebugBreakpoint} */ @Deprecated(since = "4.7.0") protected void debugAfter( Exchange exchange, Processor processor, ProcessorDefinition definition, String id, String label, long timeTaken) { } Set getCreatedRoutes() { return createdRoutes; } private void assertTestClassCamelContextMatchesAppCamelContext() { // Test classes must use the same CamelContext as the application under test Assertions.assertEquals(context, super.context, "CamelQuarkusTestSupport uses a different CamelContext compared to the application under test"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy