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

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/deltaspike.git

commit d1359dc571083cc1bfaa28e2816cfc34a360232d
Author: Mark Struberg <[email protected]>
AuthorDate: Fri May 8 15:59:12 2026 +0200

    DELTASPIKE-1366 adding Junit5 support
---
 deltaspike/modules/pom.xml                         |   1 +
 deltaspike/modules/test-control5/api/pom.xml       |  70 +++
 .../deltaspike/testcontrol5/api/TestControl.java   |  79 +++
 .../testcontrol5/api/junit/CdiTestExtension.java   | 628 +++++++++++++++++++++
 .../api/junit/CdiTestSuiteExtension.java           | 258 +++++++++
 .../testcontrol5/api/junit/TestBaseConfig.java     |  71 +++
 .../api/literal/TestControlLiteral.java            |  76 +++
 .../api/mock/ApplicationMockManager.java           |  28 +
 .../testcontrol5/api/mock/DynamicMockManager.java  |  24 +
 .../testcontrol5/api/mock/TypedMock.java           |  34 ++
 .../testcontrol5/spi/ExternalContainer.java        |  34 ++
 .../deltaspike/testcontrol5/spi/TestAware.java     |  28 +
 .../testcontrol5/spi/TestControlValidator.java     |  27 +
 .../spi/junit/TestStatementDecoratorFactory.java   |  28 +
 .../testcontrol5/spi/mock/MockFilter.java          |  29 +
 deltaspike/modules/test-control5/impl/pom.xml      | 141 +++++
 .../impl/mock/AbstractMockManager.java             | 112 ++++
 .../testcontrol5/impl/mock/BeanCacheKey.java       | 265 +++++++++
 .../testcontrol5/impl/mock/DefaultMockFilter.java  | 172 ++++++
 .../impl/mock/MockAwareInjectionTargetWrapper.java |  99 ++++
 .../impl/mock/MockAwareProducerWrapper.java        |  82 +++
 .../testcontrol5/impl/mock/MockExtension.java      | 157 ++++++
 .../impl/mock/SimpleApplicationMockManager.java    |  30 +
 .../testcontrol5/impl/mock/SimpleMockManager.java  |  47 ++
 .../impl/request/ContextControlDecorator.java      | 115 ++++
 .../StandardContextTestControlValidator.java       | 107 ++++
 deltaspike/modules/{ => test-control5}/pom.xml     |  21 +-
 27 files changed, 2749 insertions(+), 14 deletions(-)

diff --git a/deltaspike/modules/pom.xml b/deltaspike/modules/pom.xml
index d4017033f..b448d066d 100644
--- a/deltaspike/modules/pom.xml
+++ b/deltaspike/modules/pom.xml
@@ -43,5 +43,6 @@
         <module>data</module>
         <module>scheduler</module>
         <module>test-control</module>
+        <module>test-control5</module>
     </modules>
 </project>
diff --git a/deltaspike/modules/test-control5/api/pom.xml 
b/deltaspike/modules/test-control5/api/pom.xml
new file mode 100644
index 000000000..21ceb3e90
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements. See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership. The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License. You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied. See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.deltaspike.modules</groupId>
+        <artifactId>test-control5-module-project</artifactId>
+        <version>2.0.2-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.deltaspike.modules</groupId>
+    <artifactId>deltaspike-test-control5-module-api</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Apache DeltaSpike Test-Control5-Module API (JUnit 5)</name>
+
+    <properties>
+        <deltaspike.osgi.export.pkg>
+            org.apache.deltaspike.testcontrol5.*
+        </deltaspike.osgi.export.pkg>
+        <deltaspike.osgi.import>
+            jakarta.enterprise.inject,
+            !org.apache.deltaspike.testcontrol5.*,
+            *
+        </deltaspike.osgi.import>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.deltaspike.core</groupId>
+            <artifactId>deltaspike-core-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.deltaspike.cdictrl</groupId>
+            <artifactId>deltaspike-cdictrl-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>${junit5.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+
+</project>
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/TestControl.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/TestControl.java
new file mode 100644
index 000000000..816866c3f
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/TestControl.java
@@ -0,0 +1,79 @@
+/*
+ * 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.deltaspike.testcontrol5.api;
+
+import org.apache.deltaspike.core.spi.filter.ClassFilter;
+import org.apache.deltaspike.core.api.projectstage.ProjectStage;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Optional control annotation for JUnit 5 unit-tests
+ */
+
+@Target({ TYPE, METHOD })
+@Retention(RUNTIME)
+public @interface TestControl
+{
+    /**
+     * only supports contexts supported by ContextControl#startContext
+     * defaults: session- and request-scope
+     */
+    Class<? extends Annotation>[] startScopes() default { };
+
+    //TODO discuss callbacks
+
+    Class<? extends ProjectStage> projectStage() default 
ProjectStage.UnitTest.class;
+
+    /**
+     * only supported on test-class-level
+     */
+    Class<? extends Handler> logHandler() default ConsoleHandler.class;
+
+    /**
+     * Requires additional service-loader config
+     * Currently only supported on class-level
+     */
+    boolean startExternalContainers() default true;
+
+    /**
+     * allows to label alternative cdi-beans similar to global alternatives to 
bind them to 0-n tests
+     */
+    Class<? extends Label> activeAlternativeLabel() default Label.class;
+
+    //with cdi 1.1+ it can be used to implement labeled-alternatives without 
text based config
+    //(details see DELTASPIKE-1338)
+    /**
+     * low-level filter (mainly needed for special cases if 
labeled-alternatives aren't enough)
+     * @return the class-filter class which should be used for the current 
test-class
+     */
+    Class<? extends ClassFilter> classFilter() default ClassFilter.class;
+
+    interface Label
+    {
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestExtension.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestExtension.java
new file mode 100644
index 000000000..49aa23ea2
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestExtension.java
@@ -0,0 +1,628 @@
+/*
+ * 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.deltaspike.testcontrol5.api.junit;
+
+import org.apache.deltaspike.cdise.api.CdiContainer;
+import org.apache.deltaspike.cdise.api.CdiContainerLoader;
+import org.apache.deltaspike.cdise.api.ContextControl;
+import org.apache.deltaspike.core.api.projectstage.ProjectStage;
+import org.apache.deltaspike.core.api.provider.BeanProvider;
+import org.apache.deltaspike.core.util.ExceptionUtils;
+import org.apache.deltaspike.core.util.ProjectStageProducer;
+import org.apache.deltaspike.core.util.ServiceUtils;
+import org.apache.deltaspike.testcontrol5.api.TestControl;
+import org.apache.deltaspike.testcontrol5.api.literal.TestControlLiteral;
+import org.apache.deltaspike.testcontrol5.spi.ExternalContainer;
+import org.apache.deltaspike.testcontrol5.spi.TestAware;
+import org.apache.deltaspike.testcontrol5.spi.TestControlValidator;
+import 
org.apache.deltaspike.testcontrol5.spi.junit.TestStatementDecoratorFactory;
+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;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.enterprise.context.SessionScoped;
+import jakarta.inject.Singleton;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * A JUnit 5 extension to start up with a CDI or embedded JavaEE container.
+ */
+public class CdiTestExtension implements BeforeAllCallback, AfterAllCallback,
+        BeforeEachCallback, AfterEachCallback, ParameterResolver
+{
+    private static final Logger LOGGER = 
Logger.getLogger(CdiTestExtension.class.getName());
+
+    private static final boolean USE_TEST_CLASS_AS_CDI_BEAN;
+    private static final boolean ALLOW_INJECTION_POINT_MANIPULATION;
+
+    private static Set<Integer> extensionIdentities = new 
CopyOnWriteArraySet<Integer>();
+
+    static
+    {
+        USE_TEST_CLASS_AS_CDI_BEAN = 
TestBaseConfig.ContainerIntegration.USE_TEST_CLASS_AS_CDI_BEAN;
+        ALLOW_INJECTION_POINT_MANIPULATION = 
TestBaseConfig.MockIntegration.ALLOW_MANUAL_INJECTION_POINT_MANIPULATION;
+    }
+
+    private static ThreadLocal<Boolean> automaticScopeHandlingActive = new 
ThreadLocal<Boolean>();
+
+    private static ThreadLocal<CdiTestExtension> currentTestExtension = new 
ThreadLocal<CdiTestExtension>();
+
+    private List<TestStatementDecoratorFactory> statementDecoratorFactories;
+
+    private ContainerAwareTestContext testContext;
+
+    public CdiTestExtension()
+    {
+    }
+
+    @Override
+    public void beforeEach(ExtensionContext extensionContext) throws Exception
+    {
+        currentTestExtension.set(this);
+
+        TestControl testControl = extensionContext.getTestClass()
+                .map(cls -> cls.getAnnotation(TestControl.class)).orElse(null);
+
+        ContainerAwareTestContext currentTestContext =
+                new ContainerAwareTestContext(testControl, this.testContext);
+
+        extensionContext.getTestMethod().ifPresent(method ->
+            {
+                try
+                {
+                    currentTestContext.applyBeforeMethodConfig(method);
+                }
+                catch (Exception e)
+                {
+                    throw ExceptionUtils.throwAsRuntimeException(e);
+                }
+            });
+
+        this.testContext = currentTestContext;
+    }
+
+    @Override
+    public void afterEach(ExtensionContext extensionContext) throws Exception
+    {
+        try
+        {
+            if (this.testContext != null)
+            {
+                this.testContext.applyAfterMethodConfig();
+            }
+        }
+        finally
+        {
+            currentTestExtension.set(null);
+            currentTestExtension.remove();
+        }
+    }
+
+    @Override
+    public void beforeAll(ExtensionContext extensionContext) throws Exception
+    {
+        if (this.testContext == null)
+        {
+            TestControl testControl = extensionContext.getTestClass()
+                    .map(cls -> 
cls.getAnnotation(TestControl.class)).orElse(null);
+
+            this.testContext = new ContainerAwareTestContext(testControl, 
null);
+
+            Class<? extends Handler> logHandlerClass = 
this.testContext.getLogHandlerClass();
+
+            if (!Handler.class.equals(logHandlerClass))
+            {
+                try
+                {
+                    LOGGER.addHandler(logHandlerClass.newInstance());
+                }
+                catch (Exception e)
+                {
+                    throw ExceptionUtils.throwAsRuntimeException(e);
+                }
+            }
+
+            this.statementDecoratorFactories = 
ServiceUtils.loadServiceImplementations(TestStatementDecoratorFactory.class);
+            Collections.sort(this.statementDecoratorFactories, new 
Comparator<TestStatementDecoratorFactory>()
+            {
+                @Override
+                public int compare(TestStatementDecoratorFactory f1, 
TestStatementDecoratorFactory f2)
+                {
+                    return f1.getOrdinal() > f2.getOrdinal() ? 1 : -1;
+                }
+            });
+        }
+
+        
this.testContext.applyBeforeClassConfig(extensionContext.getTestClass().orElseThrow());
+    }
+
+    @Override
+    public void afterAll(ExtensionContext extensionContext) throws Exception
+    {
+        if (this.testContext != null)
+        {
+            this.testContext.applyAfterClassConfig();
+        }
+    }
+
+    @Override
+    public boolean supportsParameter(ParameterContext parameterContext, 
ExtensionContext extensionContext)
+            throws ParameterResolutionException
+    {
+        return false;
+    }
+
+    @Override
+    public Object resolveParameter(ParameterContext parameterContext, 
ExtensionContext extensionContext)
+            throws ParameterResolutionException
+    {
+        return null;
+    }
+
+    private class ContainerAwareTestContext
+    {
+        private ContainerAwareTestContext parent;
+
+        private final ProjectStage projectStage;
+        private final TestControl testControl;
+
+        private ProjectStage previousProjectStage;
+
+        private boolean containerStarted = false;
+
+        private Stack<Class<? extends Annotation>> startedScopes = new 
Stack<Class<? extends Annotation>>();
+
+        private List<ExternalContainer> externalContainers;
+
+        ContainerAwareTestContext(TestControl testControl, 
ContainerAwareTestContext parent)
+        {
+            this.parent = parent;
+
+            Class<? extends ProjectStage> foundProjectStageClass;
+            if (testControl == null)
+            {
+                this.testControl = new TestControlLiteral();
+                if (parent != null)
+                {
+                    foundProjectStageClass = parent.testControl.projectStage();
+                }
+                else
+                {
+                    foundProjectStageClass = this.testControl.projectStage();
+                }
+            }
+            else
+            {
+                this.testControl = testControl;
+                foundProjectStageClass = this.testControl.projectStage();
+            }
+            this.projectStage = 
ProjectStage.valueOf(foundProjectStageClass.getSimpleName());
+
+            ProjectStageProducer.setProjectStage(this.projectStage);
+        }
+
+        boolean isContainerStarted()
+        {
+            return this.containerStarted || (this.parent != null && 
this.parent.isContainerStarted());
+        }
+
+        Class<? extends Handler> getLogHandlerClass()
+        {
+            return this.testControl.logHandler();
+        }
+
+        void applyBeforeClassConfig(Class<?> testClass)
+        {
+            CdiContainer container = CdiContainerLoader.getCdiContainer();
+
+            if (!isContainerStarted())
+            {
+                
System.setProperty("org.jboss.weld.environment.servlet.archive.isolation", 
"false");
+
+                container.boot(CdiTestSuiteExtension.getTestContainerConfig());
+                setContainerStarted();
+
+                bootExternalContainers(testClass);
+            }
+
+            List<Class<? extends Annotation>> restrictedScopes = new 
ArrayList<Class<? extends Annotation>>();
+
+            restrictedScopes.add(ApplicationScoped.class);
+            restrictedScopes.add(Singleton.class);
+
+            if (this.parent == null && 
this.testControl.getClass().equals(TestControlLiteral.class))
+            {
+                restrictedScopes.add(RequestScoped.class);
+                restrictedScopes.add(SessionScoped.class);
+            }
+
+            startScopes(container, testClass, null, 
restrictedScopes.toArray(new Class[restrictedScopes.size()]));
+        }
+
+        private void bootExternalContainers(Class testClass)
+        {
+            if (!this.testControl.startExternalContainers())
+            {
+                return;
+            }
+
+            if (this.externalContainers == null)
+            {
+                List<ExternalContainer> configuredExternalContainers =
+                        
ServiceUtils.loadServiceImplementations(ExternalContainer.class);
+                Collections.sort(configuredExternalContainers, new 
Comparator<ExternalContainer>()
+                {
+                    @Override
+                    public int compare(ExternalContainer ec1, 
ExternalContainer ec2)
+                    {
+                        return ec1.getOrdinal() > ec2.getOrdinal() ? 1 : -1;
+                    }
+                });
+
+                this.externalContainers = new 
ArrayList<ExternalContainer>(configuredExternalContainers.size());
+
+                ExternalContainer externalContainerBean;
+                for (ExternalContainer externalContainer : 
configuredExternalContainers)
+                {
+                    externalContainerBean = 
BeanProvider.getContextualReference(externalContainer.getClass(), true);
+
+                    if (externalContainerBean != null)
+                    {
+                        this.externalContainers.add(externalContainerBean);
+                    }
+                    else
+                    {
+                        this.externalContainers.add(externalContainer);
+                    }
+                }
+
+                for (ExternalContainer externalContainer : 
this.externalContainers)
+                {
+                    try
+                    {
+                        if (externalContainer instanceof TestAware)
+                        {
+                            ((TestAware) 
externalContainer).setTestClass(testClass);
+                        }
+                        externalContainer.boot();
+                    }
+                    catch (RuntimeException e)
+                    {
+                        
Logger.getLogger(CdiTestExtension.class.getName()).log(Level.WARNING,
+                                "booting " + 
externalContainer.getClass().getName() + " failed", e);
+                    }
+                }
+            }
+        }
+
+        void applyAfterClassConfig()
+        {
+            CdiContainer container = CdiContainerLoader.getCdiContainer();
+
+            stopStartedScopes(container);
+
+            if (this.containerStarted)
+            {
+                if (CdiTestSuiteExtension.isStopContainerAllowed())
+                {
+                    shutdownExternalContainers();
+
+                    container.shutdown();
+                    CdiTestSuiteExtension.setContainerStarted(false);
+                }
+            }
+        }
+
+        private void shutdownExternalContainers()
+        {
+            if (this.externalContainers == null)
+            {
+                return;
+            }
+
+            for (ExternalContainer externalContainer : this.externalContainers)
+            {
+                try
+                {
+                    externalContainer.shutdown();
+                }
+                catch (RuntimeException e)
+                {
+                    
Logger.getLogger(CdiTestExtension.class.getName()).log(Level.WARNING,
+                            "shutting down " + 
externalContainer.getClass().getName() + " failed", e);
+                }
+            }
+        }
+
+        void applyBeforeMethodConfig(Method testMethod)
+        {
+            this.previousProjectStage = 
ProjectStageProducer.getInstance().getProjectStage();
+            ProjectStageProducer.setProjectStage(this.projectStage);
+
+            setCurrentTestMethod(testMethod);
+            startScopes(CdiContainerLoader.getCdiContainer(), 
testMethod.getDeclaringClass(), testMethod);
+        }
+
+        void applyAfterMethodConfig()
+        {
+            try
+            {
+                stopStartedScopes(CdiContainerLoader.getCdiContainer());
+            }
+            finally
+            {
+                setCurrentTestMethod(null);
+                ProjectStageProducer.setProjectStage(previousProjectStage);
+                previousProjectStage = null;
+            }
+        }
+
+        void setContainerStarted()
+        {
+            this.containerStarted = true;
+            CdiTestSuiteExtension.setContainerStarted(true);
+        }
+
+        private void startScopes(CdiContainer container,
+                                 Class testClass,
+                                 Method testMethod,
+                                 Class<? extends Annotation>... 
restrictedScopes)
+        {
+            try
+            {
+                automaticScopeHandlingActive.set(true);
+
+                ContextControl contextControl = container.getContextControl();
+
+                List<Class<? extends Annotation>> scopeClasses = new 
ArrayList<Class<? extends Annotation>>();
+
+                Collections.addAll(scopeClasses, 
this.testControl.startScopes());
+
+                if (scopeClasses.isEmpty())
+                {
+                    addScopesForDefaultBehavior(scopeClasses);
+                }
+                else
+                {
+                    List<TestControlValidator> testControlValidatorList =
+                            
ServiceUtils.loadServiceImplementations(TestControlValidator.class);
+
+                    for (TestControlValidator testControlValidator : 
testControlValidatorList)
+                    {
+                        if (testControlValidator instanceof TestAware)
+                        {
+                            if (testMethod != null)
+                            {
+                                ((TestAware) 
testControlValidator).setTestMethod(testMethod);
+                            }
+                            ((TestAware) 
testControlValidator).setTestClass(testClass);
+                        }
+                        try
+                        {
+                            testControlValidator.validate(this.testControl);
+                        }
+                        finally
+                        {
+                            if (testControlValidator instanceof TestAware)
+                            {
+                                ((TestAware) 
testControlValidator).setTestClass(null);
+                                ((TestAware) 
testControlValidator).setTestMethod(null);
+                            }
+                        }
+                    }
+                }
+
+                for (Class<? extends Annotation> scopeAnnotation : 
scopeClasses)
+                {
+                    if (this.parent != null && 
this.parent.isScopeStarted(scopeAnnotation))
+                    {
+                        continue;
+                    }
+
+                    if (isRestrictedScope(scopeAnnotation, restrictedScopes))
+                    {
+                        continue;
+                    }
+
+                    try
+                    {
+                        contextControl.stopContext(scopeAnnotation);
+
+                        contextControl.startContext(scopeAnnotation);
+                        this.startedScopes.add(scopeAnnotation);
+
+                        onScopeStarted(scopeAnnotation);
+                    }
+                    catch (RuntimeException e)
+                    {
+                        Logger logger = 
Logger.getLogger(CdiTestExtension.class.getName());
+                        logger.setLevel(Level.SEVERE);
+                        logger.log(Level.SEVERE, "failed to start scope @" + 
scopeAnnotation.getName(), e);
+                    }
+                }
+            }
+            finally
+            {
+                automaticScopeHandlingActive.set(null);
+                automaticScopeHandlingActive.remove();
+            }
+        }
+
+        private void addScopesForDefaultBehavior(List<Class<? extends 
Annotation>> scopeClasses)
+        {
+            if (this.parent != null && 
!this.parent.isScopeStarted(RequestScoped.class))
+            {
+                if (!scopeClasses.contains(RequestScoped.class))
+                {
+                    scopeClasses.add(RequestScoped.class);
+                }
+            }
+            if (this.parent != null && 
!this.parent.isScopeStarted(SessionScoped.class))
+            {
+                if (!scopeClasses.contains(SessionScoped.class))
+                {
+                    scopeClasses.add(SessionScoped.class);
+                }
+            }
+        }
+
+        private boolean isRestrictedScope(Class<? extends Annotation> 
scopeAnnotation,
+                                          Class<? extends Annotation>[] 
restrictedScopes)
+        {
+            for (Class<? extends Annotation> restrictedScope : 
restrictedScopes)
+            {
+                if (scopeAnnotation.equals(restrictedScope))
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private boolean isScopeStarted(Class<? extends Annotation> 
scopeAnnotation)
+        {
+            return this.startedScopes.contains(scopeAnnotation);
+        }
+
+        private void stopStartedScopes(CdiContainer container)
+        {
+            try
+            {
+                automaticScopeHandlingActive.set(true);
+
+                while (!this.startedScopes.empty())
+                {
+                    Class<? extends Annotation> scopeAnnotation = 
this.startedScopes.pop();
+                    try
+                    {
+                        
container.getContextControl().stopContext(scopeAnnotation);
+                        onScopeStopped(scopeAnnotation);
+                    }
+                    catch (RuntimeException e)
+                    {
+                        Logger logger = 
Logger.getLogger(CdiTestExtension.class.getName());
+                        logger.setLevel(Level.SEVERE);
+                        logger.log(Level.SEVERE, "failed to stop scope @" + 
scopeAnnotation.getName(), e);
+                    }
+                }
+            }
+            finally
+            {
+                automaticScopeHandlingActive.remove();
+                automaticScopeHandlingActive.set(null);
+            }
+        }
+
+        private void onScopeStarted(Class<? extends Annotation> scopeClass)
+        {
+            List<ExternalContainer> externalContainerList = 
collectExternalContainers(this);
+
+            for (ExternalContainer externalContainer : externalContainerList)
+            {
+                externalContainer.startScope(scopeClass);
+            }
+        }
+
+        private void onScopeStopped(Class<? extends Annotation> scopeClass)
+        {
+            List<ExternalContainer> externalContainerList = 
collectExternalContainers(this);
+
+            for (ExternalContainer externalContainer : externalContainerList)
+            {
+                externalContainer.stopScope(scopeClass);
+            }
+        }
+
+        private List<ExternalContainer> 
collectExternalContainers(ContainerAwareTestContext testContext)
+        {
+            List<ExternalContainer> result = new 
ArrayList<ExternalContainer>();
+
+            if (testContext.externalContainers != null)
+            {
+                result.addAll(testContext.externalContainers);
+            }
+
+            if (testContext.parent != null)
+            {
+                result.addAll(collectExternalContainers(testContext.parent));
+            }
+            return result;
+        }
+
+        private void setCurrentTestMethod(Method testMethod)
+        {
+            List<ExternalContainer> externalContainerList = 
collectExternalContainers(this);
+
+            for (ExternalContainer externalContainer : externalContainerList)
+            {
+                if (externalContainer instanceof TestAware)
+                {
+                    try
+                    {
+                        ((TestAware) 
externalContainer).setTestMethod(testMethod);
+                    }
+                    catch (Throwable t)
+                    {
+                        throw new RuntimeException(t.getMessage());
+                    }
+                }
+            }
+        }
+    }
+
+    public static Boolean isAutomaticScopeHandlingActive()
+    {
+        return automaticScopeHandlingActive.get();
+    }
+
+    public static List<ExternalContainer> getActiveExternalContainers()
+    {
+        CdiTestExtension cdiTestExtension = currentTestExtension.get();
+
+        if (cdiTestExtension == null ||
+                cdiTestExtension.testContext == null ||
+                cdiTestExtension.testContext.externalContainers == null)
+        {
+            return Collections.emptyList();
+        }
+
+        return 
Collections.unmodifiableList(cdiTestExtension.testContext.externalContainers);
+    }
+
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestSuiteExtension.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestSuiteExtension.java
new file mode 100644
index 000000000..18b305820
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestSuiteExtension.java
@@ -0,0 +1,258 @@
+/*
+ * 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.deltaspike.testcontrol5.api.junit;
+
+import org.apache.deltaspike.cdise.api.CdiContainer;
+import org.apache.deltaspike.cdise.api.CdiContainerLoader;
+import org.apache.deltaspike.core.api.config.ConfigResolver;
+import org.apache.deltaspike.core.api.config.PropertyLoader;
+import org.apache.deltaspike.core.spi.filter.ClassFilter;
+import org.apache.deltaspike.core.spi.activation.Deactivatable;
+import org.apache.deltaspike.core.spi.config.ConfigSource;
+import org.apache.deltaspike.core.util.ClassDeactivationUtils;
+import org.apache.deltaspike.testcontrol5.api.TestControl;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import jakarta.inject.Named;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class CdiTestSuiteExtension implements BeforeAllCallback, 
AfterAllCallback
+{
+    public static final String CUSTOM_TEST_CONTAINER_CONFIG_FILE_KEY =
+            "deltaspike.testcontrol5.test-container.config-file";
+
+    public static final String DEFAULT_TEST_CONTAINER_CONFIG_FILE_NAME =
+            "META-INF/apache-deltaspike_test-container";
+
+    private static final boolean STOP_CONTAINER;
+
+    private static final ThreadLocal<Boolean> IS_CDI_TEST_RUNNER_EXECUTION = 
new ThreadLocal<Boolean>();
+
+    private static volatile boolean containerStarted;
+
+    private Class<?> testSuiteClass;
+
+    static
+    {
+        STOP_CONTAINER = TestBaseConfig.ContainerIntegration.STOP_CONTAINER;
+    }
+
+    public CdiTestSuiteExtension()
+    {
+    }
+
+    @Override
+    public void beforeAll(ExtensionContext extensionContext) throws Exception
+    {
+        this.testSuiteClass = extensionContext.getTestClass()
+                .orElseThrow(() -> new IllegalStateException("no test-suite 
class found"));
+
+        CdiContainer container = CdiContainerLoader.getCdiContainer();
+
+        if (!containerStarted)
+        {
+            applyTestSpecificMetaData(testSuiteClass);
+
+            container.boot(getTestContainerConfig());
+            containerStarted = true;
+        }
+
+        try
+        {
+            // Tests will be executed by JUnit 5
+        }
+        finally
+        {
+            if (STOP_CONTAINER)
+            {
+                container.shutdown();
+                containerStarted = false;
+            }
+        }
+    }
+
+    @Override
+    public void afterAll(ExtensionContext extensionContext) throws Exception
+    {
+        // Cleanup if needed
+    }
+
+    public static boolean isContainerStarted()
+    {
+        return containerStarted;
+    }
+
+    static Boolean isStopContainerAllowed()
+    {
+        return STOP_CONTAINER;
+    }
+
+    static ThreadLocal<Boolean> getCdiTestRunnerExecutionRef()
+    {
+        return IS_CDI_TEST_RUNNER_EXECUTION;
+    }
+
+    static void setContainerStarted(boolean containerStarted)
+    {
+        CdiTestSuiteExtension.containerStarted = containerStarted;
+    }
+
+    public static Properties getTestContainerConfig()
+    {
+        String cdiTestRunnerConfig = 
ConfigResolver.getProjectStageAwarePropertyValue(
+                CUSTOM_TEST_CONTAINER_CONFIG_FILE_KEY, 
DEFAULT_TEST_CONTAINER_CONFIG_FILE_NAME);
+        return PropertyLoader.getProperties(cdiTestRunnerConfig);
+    }
+
+    static void applyTestSpecificMetaData(Class<?> currentAnnotationSource)
+    {
+        TestControl testControl = 
currentAnnotationSource.getAnnotation(TestControl.class);
+        String activeAlternativeLabel = 
checkForLabeledAlternativeConfig(testControl);
+
+        initTestEnvConfig(currentAnnotationSource, activeAlternativeLabel, 
testControl);
+    }
+
+    private static String checkForLabeledAlternativeConfig(TestControl 
testControl)
+    {
+        String activeAlternativeLabel = "";
+
+        if (testControl != null)
+        {
+            Class<? extends TestControl.Label> activeTypedAlternativeLabel =
+                    testControl.activeAlternativeLabel();
+
+            if (!TestControl.Label.class.equals(activeTypedAlternativeLabel))
+            {
+                Named labelName = 
activeTypedAlternativeLabel.getAnnotation(Named.class);
+
+                if (labelName != null)
+                {
+                    activeAlternativeLabel = labelName.value();
+                }
+                else
+                {
+                    String labelClassName = 
activeTypedAlternativeLabel.getSimpleName();
+                    activeAlternativeLabel = labelClassName.substring(0, 
1).toLowerCase();
+
+                    if (labelClassName.length() > 1)
+                    {
+                        activeAlternativeLabel += labelClassName.substring(1);
+                    }
+                }
+            }
+        }
+        return activeAlternativeLabel;
+    }
+
+    private static void initTestEnvConfig(Class<?> testClass, String 
activeAlternativeLabel, TestControl testControl)
+    {
+        if (ClassDeactivationUtils.isActivated(TestConfigSource.class))
+        {
+            TestConfigSource testConfigSource = null;
+
+            for (ConfigSource configSource : ConfigResolver.getConfigSources())
+            {
+                if (configSource instanceof TestConfigSource)
+                {
+                    testConfigSource = (TestConfigSource) configSource;
+                }
+            }
+
+            if (testConfigSource == null)
+            {
+                testConfigSource = new TestConfigSource();
+                
ConfigResolver.addConfigSources(Arrays.<ConfigSource>asList(testConfigSource));
+            }
+
+            testConfigSource.getProperties().put("activeAlternativeLabel", 
activeAlternativeLabel);
+
+            
testConfigSource.getProperties().put("activeAlternativeLabelSource", 
testClass.getName());
+
+            if (testControl != null)
+            {
+                
testConfigSource.getProperties().put(TestControl.class.getName(), 
testClass.getName());
+                
testConfigSource.getProperties().put(ClassFilter.class.getName(), 
testControl.classFilter().getName());
+            }
+            else
+            {
+                
testConfigSource.getProperties().put(TestControl.class.getName(), 
TestControl.class.getName());
+                
testConfigSource.getProperties().put(ClassFilter.class.getName(), 
ClassFilter.class.getName());
+            }
+        }
+        else
+        {
+            System.setProperty("activeAlternativeLabel", 
activeAlternativeLabel);
+
+            System.setProperty("activeAlternativeLabelSource", 
testClass.getName());
+
+            if (testControl != null)
+            {
+                System.setProperty(TestControl.class.getName(), 
testClass.getName());
+                System.setProperty(ClassFilter.class.getName(), 
testControl.classFilter().getName());
+            }
+            else
+            {
+                System.setProperty(TestControl.class.getName(), 
TestControl.class.getName());
+                System.setProperty(ClassFilter.class.getName(), 
ClassFilter.class.getName());
+            }
+        }
+    }
+
+    public static class TestConfigSource implements ConfigSource, Deactivatable
+    {
+        private Map<String, String> testConfig = new ConcurrentHashMap<String, 
String>();
+
+        @Override
+        public int getOrdinal()
+        {
+            return Integer.MIN_VALUE;
+        }
+
+        @Override
+        public Map<String, String> getProperties()
+        {
+            return testConfig;
+        }
+
+        @Override
+        public String getPropertyValue(String key)
+        {
+            return testConfig.get(key);
+        }
+
+        @Override
+        public String getConfigName()
+        {
+            return "ds-test5-config";
+        }
+
+        @Override
+        public boolean isScannable()
+        {
+            return true;
+        }
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/TestBaseConfig.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/TestBaseConfig.java
new file mode 100644
index 000000000..0928e3fae
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/TestBaseConfig.java
@@ -0,0 +1,71 @@
+/*
+ * 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.deltaspike.testcontrol5.api.junit;
+
+import org.apache.deltaspike.core.api.config.ConfigResolver;
+import org.apache.deltaspike.core.api.config.base.DeltaSpikeBaseConfig;
+
+public interface TestBaseConfig extends DeltaSpikeBaseConfig
+{
+    interface ContainerIntegration
+    {
+        //default is false to improve the compatibility with @Before and @After
+        Boolean USE_TEST_CLASS_AS_CDI_BEAN = 
ConfigResolver.resolve("deltaspike.testcontrol5.use_test_class_as_cdi_bean")
+                .as(Boolean.class)
+                .withCurrentProjectStage(true)
+                .withDefault(Boolean.FALSE)
+                .getValue();
+
+        Boolean STOP_CONTAINER = 
ConfigResolver.resolve("deltaspike.testcontrol5.stop_container")
+                .as(Boolean.class)
+                .withCurrentProjectStage(true)
+                .withDefault(Boolean.TRUE)
+                .getValue();
+    }
+
+    interface MockIntegration
+    {
+        String ALLOW_MOCKED_BEANS_KEY = 
"deltaspike.testcontrol5.mock-support.allow_mocked_beans";
+        String ALLOW_MOCKED_PRODUCERS_KEY = 
"deltaspike.testcontrol5.mock-support.allow_mocked_producers";
+        String ALLOW_MANUAL_INJECTION_POINT_MANIPULATION_KEY =
+            
"deltaspike.testcontrol5.mock-support.allow_manual_injection-point_manipulation";
+
+        Boolean ALLOW_MOCKED_BEANS = 
ConfigResolver.resolve(ALLOW_MOCKED_BEANS_KEY)
+                .as(Boolean.class)
+                .withCurrentProjectStage(true)
+                .withDefault(Boolean.FALSE)
+                .getValue();
+
+        Boolean ALLOW_MOCKED_PRODUCERS = 
ConfigResolver.resolve(ALLOW_MOCKED_PRODUCERS_KEY)
+                .as(Boolean.class)
+                .withCurrentProjectStage(true)
+                .withDefault(Boolean.FALSE)
+                .getValue();
+
+        //if enabled it's possible to change the value of injection-points 
after the injection-process and
+        //before test-execution. that allows to replace injection-points (e.g. 
with a mock) conditionally
+        //via a test-rule or @Before
+        Boolean ALLOW_MANUAL_INJECTION_POINT_MANIPULATION =
+            
ConfigResolver.resolve(ALLOW_MANUAL_INJECTION_POINT_MANIPULATION_KEY)
+                .as(Boolean.class)
+                .withCurrentProjectStage(true)
+                .withDefault(Boolean.FALSE)
+                .getValue();
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/literal/TestControlLiteral.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/literal/TestControlLiteral.java
new file mode 100644
index 000000000..a5edd44e2
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/literal/TestControlLiteral.java
@@ -0,0 +1,76 @@
+/*
+ * 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.deltaspike.testcontrol5.api.literal;
+
+import org.apache.deltaspike.core.spi.filter.ClassFilter;
+import org.apache.deltaspike.core.api.projectstage.ProjectStage;
+import org.apache.deltaspike.core.util.metadata.AnnotationInstanceProvider;
+import org.apache.deltaspike.testcontrol5.api.TestControl;
+
+import jakarta.enterprise.util.AnnotationLiteral;
+import java.lang.annotation.Annotation;
+import java.util.logging.Handler;
+
+public class TestControlLiteral extends AnnotationLiteral<TestControl> 
implements TestControl
+{
+    private static final long serialVersionUID = 6684011035751678259L;
+
+    private final TestControl defaultInstance;
+
+    public TestControlLiteral()
+    {
+        this.defaultInstance = 
AnnotationInstanceProvider.of(TestControl.class);
+    }
+
+    @Override
+    public Class<? extends Annotation>[] startScopes()
+    {
+        return defaultInstance.startScopes();
+    }
+
+    @Override
+    public Class<? extends ProjectStage> projectStage()
+    {
+        return defaultInstance.projectStage();
+    }
+
+    @Override
+    public Class<? extends Handler> logHandler()
+    {
+        return defaultInstance.logHandler();
+    }
+
+    @Override
+    public boolean startExternalContainers()
+    {
+        return defaultInstance.startExternalContainers();
+    }
+
+    @Override
+    public Class<? extends Label> activeAlternativeLabel()
+    {
+        return defaultInstance.activeAlternativeLabel();
+    }
+
+    @Override
+    public Class<? extends ClassFilter> classFilter()
+    {
+        return defaultInstance.classFilter();
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/ApplicationMockManager.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/ApplicationMockManager.java
new file mode 100644
index 000000000..7dff1f36d
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/ApplicationMockManager.java
@@ -0,0 +1,28 @@
+/*
+ * 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.deltaspike.testcontrol5.api.mock;
+
+import java.lang.annotation.Annotation;
+
+public interface ApplicationMockManager
+{
+    void addMock(Object mockInstance, Annotation... qualifiers);
+
+    <T> T getMock(Class<T> beanClass, Annotation... qualifiers);
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/DynamicMockManager.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/DynamicMockManager.java
new file mode 100644
index 000000000..c41178eb8
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/DynamicMockManager.java
@@ -0,0 +1,24 @@
+/*
+ * 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.deltaspike.testcontrol5.api.mock;
+
+public interface DynamicMockManager extends ApplicationMockManager
+{
+    void reset();
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/TypedMock.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/TypedMock.java
new file mode 100644
index 000000000..ff06c76f6
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/TypedMock.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.deltaspike.testcontrol5.api.mock;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target({ FIELD, METHOD, TYPE })
+@Retention(RUNTIME)
+public @interface TypedMock
+{
+    Class<?>[] value() default { };
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/ExternalContainer.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/ExternalContainer.java
new file mode 100644
index 000000000..189482b14
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/ExternalContainer.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.deltaspike.testcontrol5.spi;
+
+import java.lang.annotation.Annotation;
+
+public interface ExternalContainer
+{
+    void boot();
+
+    void shutdown();
+
+    int getOrdinal();
+
+    void startScope(Class<? extends Annotation> scopeClass);
+
+    void stopScope(Class<? extends Annotation> scopeClass);
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestAware.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestAware.java
new file mode 100644
index 000000000..19de3b912
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestAware.java
@@ -0,0 +1,28 @@
+/*
+ * 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.deltaspike.testcontrol5.spi;
+
+import java.lang.reflect.Method;
+
+public interface TestAware
+{
+    void setTestClass(Class testClass);
+
+    void setTestMethod(Method testMethod);
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestControlValidator.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestControlValidator.java
new file mode 100644
index 000000000..0475047a5
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestControlValidator.java
@@ -0,0 +1,27 @@
+/*
+ * 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.deltaspike.testcontrol5.spi;
+
+import org.apache.deltaspike.core.spi.activation.Deactivatable;
+import org.apache.deltaspike.testcontrol5.api.TestControl;
+
+public interface TestControlValidator extends Deactivatable
+{
+    void validate(TestControl testControl);
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/junit/TestStatementDecoratorFactory.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/junit/TestStatementDecoratorFactory.java
new file mode 100644
index 000000000..ba68a53a3
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/junit/TestStatementDecoratorFactory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.deltaspike.testcontrol5.spi.junit;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public interface TestStatementDecoratorFactory
+{
+    ExtensionContext createContext(ExtensionContext extensionContext);
+
+    int getOrdinal();
+}
diff --git 
a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/mock/MockFilter.java
 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/mock/MockFilter.java
new file mode 100644
index 000000000..5b804f093
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/mock/MockFilter.java
@@ -0,0 +1,29 @@
+/*
+ * 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.deltaspike.testcontrol5.spi.mock;
+
+import org.apache.deltaspike.core.spi.activation.Deactivatable;
+
+import jakarta.enterprise.inject.spi.Annotated;
+import jakarta.enterprise.inject.spi.BeanManager;
+
+public interface MockFilter extends Deactivatable
+{
+    boolean isMockedImplementationSupported(BeanManager beanManager, Annotated 
annotated);
+}
diff --git a/deltaspike/modules/test-control5/impl/pom.xml 
b/deltaspike/modules/test-control5/impl/pom.xml
new file mode 100644
index 000000000..a9cbed09c
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/pom.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements. See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership. The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License. You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied. See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.deltaspike.modules</groupId>
+        <artifactId>test-control5-module-project</artifactId>
+        <version>2.0.2-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.deltaspike.modules</groupId>
+    <artifactId>deltaspike-test-control5-module-impl</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Apache DeltaSpike Test-Control5-Module Impl (JUnit 5)</name>
+
+    <properties>
+        <deltaspike.osgi.export.pkg>
+            org.apache.deltaspike.testcontrol5.impl.*
+        </deltaspike.osgi.export.pkg>
+        <deltaspike.osgi.import>
+            !org.apache.deltaspike.testcontrol5.impl.*,
+            *
+        </deltaspike.osgi.import>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.deltaspike.modules</groupId>
+            <artifactId>deltaspike-test-control5-module-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.deltaspike.cdictrl</groupId>
+            <artifactId>deltaspike-cdictrl-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-jpa_2.2_spec</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>${junit5.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>jakarta.el</groupId>
+            <artifactId>jakarta.el-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tomcat</groupId>
+            <artifactId>tomcat-servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.deltaspike.core</groupId>
+            <artifactId>deltaspike-core-impl</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+    </dependencies>
+
+
+    <profiles>
+        <profile>
+            <id>OWB</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.openwebbeans</groupId>
+                    <artifactId>openwebbeans-impl</artifactId>
+                    <scope>test</scope>
+                </dependency>
+
+                <dependency>
+                    <groupId>org.apache.openwebbeans</groupId>
+                    <artifactId>openwebbeans-spi</artifactId>
+                    <scope>test</scope>
+                </dependency>
+
+                <dependency>
+                    <groupId>org.apache.deltaspike.cdictrl</groupId>
+                    <artifactId>deltaspike-cdictrl-owb</artifactId>
+                    <version>${project.version}</version>
+                    <scope>test</scope>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>Weld</id>
+            <dependencies>
+                <!-- Adding a test dependency needed for Weld -->
+                <dependency>
+                    <groupId>org.apache.deltaspike.cdictrl</groupId>
+                    <artifactId>deltaspike-cdictrl-weld</artifactId>
+                    <version>${project.version}</version>
+                    <scope>test</scope>
+                </dependency>
+
+                <!-- The remaining bits of this profile are located under 
deltaspike/parent/code/pom.xml -->
+            </dependencies>
+        </profile>
+    </profiles>
+
+</project>
diff --git 
a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/AbstractMockManager.java
 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/AbstractMockManager.java
new file mode 100644
index 000000000..d486d27b5
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/AbstractMockManager.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.deltaspike.testcontrol5.impl.mock;
+
+import jakarta.enterprise.inject.Typed;
+import org.apache.deltaspike.testcontrol5.api.junit.TestBaseConfig;
+import org.apache.deltaspike.testcontrol5.api.mock.DynamicMockManager;
+import org.apache.deltaspike.testcontrol5.api.mock.TypedMock;
+
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class AbstractMockManager implements DynamicMockManager
+{
+    private Map<BeanCacheKey, Object> registeredMocks = new 
HashMap<BeanCacheKey, Object>();
+
+    @Override
+    public void addMock(Object mockInstance, Annotation... qualifiers)
+    {
+        if (!TestBaseConfig.MockIntegration.ALLOW_MOCKED_BEANS &&
+            !TestBaseConfig.MockIntegration.ALLOW_MOCKED_PRODUCERS)
+        {
+            throw new IllegalStateException("The support for mocked CDI-Beans 
is disabled " +
+                "due to a reduced portability across different 
CDI-implementations. " +
+                "Please set '" + 
TestBaseConfig.MockIntegration.ALLOW_MOCKED_BEANS_KEY + "' and/or '" +
+                TestBaseConfig.MockIntegration.ALLOW_MOCKED_PRODUCERS_KEY + "' 
to 'true' " +
+                "(in 'META-INF/apache-deltaspike.properties') on your 
test-classpath.");
+        }
+
+        Class<?> mockClass = mockInstance.getClass();
+        Class<?> beanClass = mockClass.getSuperclass();
+
+        if (beanClass == null)
+        {
+            beanClass = mockClass;
+        }
+        if (Object.class.equals(beanClass))
+        {
+            throw new 
IllegalArgumentException(mockInstance.getClass().getName() +
+                " isn't a supported approach for mocking -> please extend from 
the original class.");
+        }
+
+        TypedMock typedMock = mockClass.getAnnotation(TypedMock.class);
+
+        if (typedMock == null)
+        {
+            typedMock = beanClass.getAnnotation(TypedMock.class);
+        }
+
+        Class[] specifiedTypes = null;
+
+        if (typedMock != null)
+        {
+            specifiedTypes = typedMock.value();
+        }
+        else
+        {
+            Typed typed = mockClass.getAnnotation(Typed.class);
+
+            if (typed == null || typed.value().length == 0)
+            {
+                typed = beanClass.getAnnotation(Typed.class);
+            }
+
+            if (typed != null && typed.value().length > 0)
+            {
+                specifiedTypes = typed.value();
+            }
+        }
+
+        if (specifiedTypes != null)
+        {
+            for (Class typedClass : specifiedTypes)
+            {
+                this.registeredMocks.put(new BeanCacheKey(typedClass, 
qualifiers), mockInstance);
+            }
+        }
+        else
+        {
+            this.registeredMocks.put(new BeanCacheKey(beanClass, qualifiers), 
mockInstance);
+        }
+    }
+
+    @Override
+    public <T> T getMock(Class<T> beanClass, Annotation... qualifiers)
+    {
+        return (T)this.registeredMocks.get(new BeanCacheKey(beanClass, 
qualifiers));
+    }
+
+    @Override
+    public void reset()
+    {
+        this.registeredMocks.clear();
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/BeanCacheKey.java
 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/BeanCacheKey.java
new file mode 100644
index 000000000..0461a4d3f
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/BeanCacheKey.java
@@ -0,0 +1,265 @@
+/*
+ * 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.deltaspike.testcontrol5.impl.mock;
+
+import org.apache.deltaspike.core.util.ReflectionUtils;
+
+import jakarta.enterprise.util.Nonbinding;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Comparator;
+
+public class BeanCacheKey
+{
+    private static final Comparator<Annotation> ANNOTATION_COMPARATOR = new 
AnnotationComparator();
+
+    private final Type type;
+    private final Annotation qualifier;
+    private final Annotation qualifiers[];
+    private final int hashCode;
+
+    public BeanCacheKey(Type type, Annotation... qualifiers)
+    {
+        this.type = type;
+        final int length = qualifiers != null ? qualifiers.length : 0;
+        if (length == 0)
+        {
+            qualifier = null;
+            this.qualifiers = null;
+        }
+        else if (length == 1)
+        {
+            qualifier = qualifiers[0];
+            this.qualifiers = null;
+        }
+        else
+        {
+            qualifier = null;
+            this.qualifiers = new Annotation[length];
+            System.arraycopy(qualifiers, 0, this.qualifiers, 0, length);
+            Arrays.sort(this.qualifiers, ANNOTATION_COMPARATOR);
+        }
+
+        hashCode = computeHashCode();
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+
+        BeanCacheKey cacheKey = (BeanCacheKey) o;
+
+        if (!type.equals(cacheKey.type))
+        {
+            return false;
+        }
+        if (qualifier != null ? !qualifierEquals(qualifier, 
cacheKey.qualifier) : cacheKey.qualifier != null)
+        {
+            return false;
+        }
+        if (!qualifierArrayEquals(qualifiers, cacheKey.qualifiers))
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean qualifierArrayEquals(Annotation[] qualifiers1, 
Annotation[] qualifiers2)
+    {
+        if (qualifiers1 == qualifiers2)
+        {
+            return true;
+        }
+        else if (qualifiers1 == null || qualifiers2 == null)
+        {
+            return false;
+        }
+        if (qualifiers1.length != qualifiers2.length)
+        {
+            return false;
+        }
+        for (int i = 0; i < qualifiers1.length; i++)
+        {
+            Annotation a1 = qualifiers1[i];
+            Annotation a2 = qualifiers2[i];
+            if (a1 == null ? a2 != null : !qualifierEquals(a1, a2))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return hashCode;
+    }
+
+    private int computeHashCode()
+    {
+        int computedHashCode = 31 * 
ReflectionUtils.calculateHashCodeOfType(type);
+        if (qualifier != null)
+        {
+            computedHashCode = 31 * computedHashCode + 
getQualifierHashCode(qualifier);
+        }
+        if (qualifiers != null)
+        {
+            for (int i = 0; i < qualifiers.length; i++)
+            {
+                computedHashCode = 31 * computedHashCode + 
getQualifierHashCode(qualifiers[i]);
+            }
+        }
+        return computedHashCode;
+    }
+
+    private int getQualifierHashCode(Annotation a)
+    {
+        return ReflectionUtils.calculateHashCodeOfAnnotation(a, true);
+    }
+
+    private boolean qualifierEquals(Annotation qualifier1, Annotation 
qualifier2)
+    {
+        return ANNOTATION_COMPARATOR.compare(qualifier1, qualifier2) == 0;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "BeanCacheKey{" + "type=" + type + ", qualifiers="
+                + (qualifiers == null ? qualifier : Arrays.asList(qualifiers)) 
+ ", hashCode=" + hashCode + '}';
+    }
+
+    private static class AnnotationComparator implements Comparator<Annotation>
+    {
+        @Override
+        public int compare(Annotation annotation1, Annotation annotation2)
+        {
+            final Class<? extends Annotation> type1 = 
annotation1.annotationType();
+            final Class<? extends Annotation> type2 = 
annotation2.annotationType();
+            final int temp = type1.getName().compareTo(type2.getName());
+            if (temp != 0)
+            {
+                return temp;
+            }
+            final Method[] member1 = type1.getDeclaredMethods();
+            final Method[] member2 = type2.getDeclaredMethods();
+
+            int i = 0;
+            int j = 0;
+            final int length1 = member1.length;
+            final int length2 = member2.length;
+
+            for (;; i++, j++)
+            {
+                while (i < length1 && 
member1[i].isAnnotationPresent(Nonbinding.class))
+                {
+                    i++;
+                }
+                while (j < length2 && 
member2[j].isAnnotationPresent(Nonbinding.class))
+                {
+                    j++;
+                }
+                if (i >= length1 && j >= length2)
+                {
+                    return 0;
+                }
+                else if (i >= length1)
+                {
+                    return 1;
+                }
+                else if (j >= length2)
+                {
+                    return -1;
+                }
+                else
+                {
+                    int c = 
member1[i].getName().compareTo(member2[j].getName());
+                    if (c != 0)
+                    {
+                        return c;
+                    }
+                    final Object value1 = 
ReflectionUtils.invokeMethod(annotation1, member1[i], Object.class, true);
+                    final Object value2 = 
ReflectionUtils.invokeMethod(annotation2, member2[j], Object.class, true);
+                    assert value1.getClass().equals(value2.getClass());
+
+                    if (value1 instanceof Comparable)
+                    {
+                        c = ((Comparable)value1).compareTo(value2);
+                        if (c != 0)
+                        {
+                            return c;
+                        }
+                    }
+                    else if (value1.getClass().isArray())
+                    {
+                        c = value1.getClass().getComponentType().getName()
+                                
.compareTo(value2.getClass().getComponentType().getName());
+                        if (c != 0)
+                        {
+                            return c;
+                        }
+
+                        final int length = Array.getLength(value1);
+                        c = length - Array.getLength(value2);
+                        if (c != 0)
+                        {
+                            return c;
+                        }
+                        for (int k = 0; k < length; k++)
+                        {
+                            c = ((Comparable)Array.get(value1, 
k)).compareTo(Array.get(value2, k));
+                            if (c != 0)
+                            {
+                                return c;
+                            }
+                        }
+
+                    }
+                    else if (value1 instanceof Class)
+                    {
+
+                        c = ((Class)value1).getName().compareTo(((Class) 
value2).getName());
+                        if (c != 0)
+                        {
+                            return c;
+                        }
+                    }
+                    else
+                    {
+                        assert false;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/DefaultMockFilter.java
 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/DefaultMockFilter.java
new file mode 100644
index 000000000..14044ad5c
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/DefaultMockFilter.java
@@ -0,0 +1,172 @@
+/*
+ * 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.deltaspike.testcontrol5.impl.mock;
+
+import org.apache.deltaspike.testcontrol5.api.junit.TestBaseConfig;
+import org.apache.deltaspike.testcontrol5.spi.mock.MockFilter;
+
+import jakarta.enterprise.inject.Produces;
+import jakarta.enterprise.inject.spi.Annotated;
+import jakarta.enterprise.inject.spi.AnnotatedField;
+import jakarta.enterprise.inject.spi.AnnotatedMember;
+import jakarta.enterprise.inject.spi.AnnotatedMethod;
+import jakarta.enterprise.inject.spi.AnnotatedType;
+import jakarta.enterprise.inject.spi.BeanManager;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Logger;
+
+public class DefaultMockFilter implements MockFilter
+{
+    private static final Logger LOG = 
Logger.getLogger(DefaultMockFilter.class.getName());
+
+    private static final String DS_BASE_PACKAGE = "org.apache.deltaspike.";
+    private static final String JAVA_BASE_PACKAGE = "java.";
+    private static final String JAVAX_BASE_PACKAGE = "javax.";
+    private static final String JAKARTA_BASE_PACKAGE = "jakarta.";
+    private static final String EJB_BASE_PACKAGE = "javax.ejb.";
+    private static final String OWB_BASE_PACKAGE = "org.apache.webbeans.";
+    private static final String WELD_BASE_PACKAGE = "org.jboss.weld.";
+
+    @Override
+    public boolean isMockedImplementationSupported(BeanManager beanManager, 
Annotated annotated)
+    {
+        if (!isMockSupportEnabled(annotated))
+        {
+            return false;
+        }
+
+        Class origin = null;
+        if (annotated instanceof AnnotatedType)
+        {
+            origin = ((AnnotatedType)annotated).getJavaClass();
+            Set<Annotation> annotations = new HashSet<Annotation>();
+            annotations.addAll(annotated.getAnnotations());
+
+            for (AnnotatedMethod annotatedMethod :
+                
(Set<jakarta.enterprise.inject.spi.AnnotatedMethod>)((AnnotatedType) 
annotated).getMethods())
+            {
+                annotations.addAll(annotatedMethod.getAnnotations());
+            }
+
+            if (isEjbOrAnnotatedTypeWithInterceptorAnnotation(
+                beanManager, annotations, origin.getName()))
+            {
+                return false;
+            }
+        }
+        else if (annotated instanceof AnnotatedMember)
+        {
+            Member member = ((AnnotatedMember)annotated).getJavaMember();
+            origin = member.getDeclaringClass();
+            if (isEjbOrAnnotatedTypeWithInterceptorAnnotation(
+                beanManager, annotated.getAnnotations(), member.toString()))
+            {
+                return false;
+            }
+        }
+
+        if (origin != null && origin.getPackage() == null)
+        {
+            LOG.warning("Please don't use the default-package for " + 
origin.getName());
+            return true;
+        }
+
+        return origin != null && 
!isInternalPackage(origin.getPackage().getName());
+    }
+
+    protected boolean isMockSupportEnabled(Annotated annotated)
+    {
+        if ((annotated instanceof AnnotatedMethod || annotated instanceof 
AnnotatedField) &&
+                annotated.getAnnotation(Produces.class) != null)
+        {
+            return TestBaseConfig.MockIntegration.ALLOW_MOCKED_PRODUCERS;
+        }
+        else
+        {
+            return TestBaseConfig.MockIntegration.ALLOW_MOCKED_BEANS;
+        }
+    }
+
+    protected boolean 
isEjbOrAnnotatedTypeWithInterceptorAnnotation(BeanManager beanManager,
+                                                                      
Set<Annotation> annotations,
+                                                                      String 
origin)
+    {
+        for (Annotation annotation : annotations)
+        {
+            if 
(annotation.annotationType().getName().startsWith(EJB_BASE_PACKAGE))
+            {
+                return true;
+            }
+
+            if (isStandardAnnotation(annotation))
+            {
+                continue;
+            }
+
+            if (beanManager.isInterceptorBinding(annotation.annotationType()) 
||
+                (beanManager.isStereotype(annotation.annotationType()) &&
+                    isStereotypeWithInterceptor(annotation, beanManager)))
+            {
+                LOG.warning("Skip mocking intercepted bean " + origin);
+
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected boolean isStereotypeWithInterceptor(Annotation 
stereotypeAnnotation, BeanManager beanManager)
+    {
+        for (Annotation annotation : 
stereotypeAnnotation.annotationType().getAnnotations())
+        {
+            if (isStandardAnnotation(annotation))
+            {
+                continue;
+            }
+
+            if (beanManager.isInterceptorBinding(annotation.annotationType()) 
||
+                isStereotypeWithInterceptor(annotation, beanManager))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected boolean isStandardAnnotation(Annotation annotation)
+    {
+        return 
annotation.annotationType().getName().startsWith(JAVA_BASE_PACKAGE) ||
+            
annotation.annotationType().getName().startsWith(JAVAX_BASE_PACKAGE) ||
+            
annotation.annotationType().getName().startsWith(JAKARTA_BASE_PACKAGE);
+    }
+
+    protected boolean isInternalPackage(String packageName)
+    {
+        return packageName.startsWith(OWB_BASE_PACKAGE) || 
packageName.startsWith(WELD_BASE_PACKAGE) ||
+            isDeltaSpikePackage(packageName);
+    }
+
+    protected boolean isDeltaSpikePackage(String packageName)
+    {
+        return packageName.startsWith(DS_BASE_PACKAGE);
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareInjectionTargetWrapper.java
 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareInjectionTargetWrapper.java
new file mode 100644
index 000000000..c7e5246cf
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareInjectionTargetWrapper.java
@@ -0,0 +1,99 @@
+/*
+ * 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.deltaspike.testcontrol5.impl.mock;
+
+import org.apache.deltaspike.core.api.provider.BeanProvider;
+import org.apache.deltaspike.testcontrol5.api.mock.DynamicMockManager;
+
+import jakarta.enterprise.context.spi.CreationalContext;
+import jakarta.enterprise.inject.spi.BeanManager;
+import jakarta.enterprise.inject.spi.InjectionPoint;
+import jakarta.enterprise.inject.spi.InjectionTarget;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+
+public class MockAwareInjectionTargetWrapper<T> implements InjectionTarget<T>
+{
+    private BeanManager beanManager;
+    private final InjectionTarget<T> wrapped;
+    private final List<Type> beanTypes;
+    private final List<Annotation> qualifiers;
+
+    public MockAwareInjectionTargetWrapper(BeanManager beanManager,
+                                           InjectionTarget<T> wrapped,
+                                           List<Type> beanTypes,
+                                           List<Annotation> qualifiers)
+    {
+        this.beanManager = beanManager;
+        this.wrapped = wrapped;
+        this.beanTypes = beanTypes;
+        this.qualifiers = qualifiers;
+    }
+
+    @Override
+    public T produce(CreationalContext<T> creationalContext)
+    {
+        DynamicMockManager mockManager =
+            BeanProvider.getContextualReference(this.beanManager, 
DynamicMockManager.class, false);
+
+        for (Type beanType : this.beanTypes)
+        {
+            Object mockInstance = mockManager.getMock(
+                    (Class)beanType, this.qualifiers.toArray(new 
Annotation[this.qualifiers.size()]));
+
+            if (mockInstance != null)
+            {
+                return (T)mockInstance;
+            }
+        }
+        return wrapped.produce(creationalContext);
+    }
+
+    @Override
+    public void inject(T instance, CreationalContext<T> ctx)
+    {
+        wrapped.inject(instance, ctx);
+    }
+
+    @Override
+    public void postConstruct(T instance)
+    {
+        wrapped.postConstruct(instance);
+    }
+
+    @Override
+    public void preDestroy(T instance)
+    {
+        wrapped.preDestroy(instance);
+    }
+
+    @Override
+    public void dispose(T instance)
+    {
+        wrapped.dispose(instance);
+    }
+
+    @Override
+    public Set<InjectionPoint> getInjectionPoints()
+    {
+        return wrapped.getInjectionPoints();
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareProducerWrapper.java
 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareProducerWrapper.java
new file mode 100644
index 000000000..0b4ec4653
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareProducerWrapper.java
@@ -0,0 +1,82 @@
+/*
+ * 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.deltaspike.testcontrol5.impl.mock;
+
+import org.apache.deltaspike.core.api.provider.BeanProvider;
+import org.apache.deltaspike.testcontrol5.api.mock.DynamicMockManager;
+
+import jakarta.enterprise.context.spi.CreationalContext;
+import jakarta.enterprise.inject.spi.BeanManager;
+import jakarta.enterprise.inject.spi.InjectionPoint;
+import jakarta.enterprise.inject.spi.Producer;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+
+public class MockAwareProducerWrapper<T> implements Producer<T>
+{
+    private final BeanManager beanManager;
+    private final Producer<T> wrapped;
+    private final List<Type> beanTypes;
+    private final List<Annotation> qualifiers;
+
+    public MockAwareProducerWrapper(BeanManager beanManager,
+                                    Producer<T> wrapped,
+                                    List<Type> beanTypes,
+                                    List<Annotation> qualifiers)
+    {
+        this.beanManager = beanManager;
+        this.wrapped = wrapped;
+        this.beanTypes = beanTypes;
+        this.qualifiers = qualifiers;
+    }
+
+    @Override
+    public T produce(CreationalContext<T> creationalContext)
+    {
+        DynamicMockManager mockManager =
+            BeanProvider.getContextualReference(this.beanManager, 
DynamicMockManager.class, false);
+
+        for (Type beanType : this.beanTypes)
+        {
+            Object mockInstance = mockManager.getMock(
+                (Class)beanType, this.qualifiers.toArray(new 
Annotation[this.qualifiers.size()]));
+
+            if (mockInstance != null)
+            {
+                return (T)mockInstance;
+            }
+        }
+
+        return wrapped.produce(creationalContext);
+    }
+
+    @Override
+    public void dispose(T instance)
+    {
+        wrapped.dispose(instance);
+    }
+
+    @Override
+    public Set<InjectionPoint> getInjectionPoints()
+    {
+        return wrapped.getInjectionPoints();
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockExtension.java
 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockExtension.java
new file mode 100644
index 000000000..b94d29604
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockExtension.java
@@ -0,0 +1,157 @@
+/*
+ * 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.deltaspike.testcontrol5.impl.mock;
+
+import jakarta.enterprise.inject.Typed;
+import org.apache.deltaspike.core.spi.activation.Deactivatable;
+import org.apache.deltaspike.core.util.ClassDeactivationUtils;
+import org.apache.deltaspike.core.util.ServiceUtils;
+import org.apache.deltaspike.testcontrol5.spi.mock.MockFilter;
+
+import jakarta.enterprise.event.Observes;
+import jakarta.enterprise.inject.spi.AnnotatedMember;
+import jakarta.enterprise.inject.spi.BeanManager;
+import jakarta.enterprise.inject.spi.BeforeBeanDiscovery;
+import jakarta.enterprise.inject.spi.Extension;
+import jakarta.enterprise.inject.spi.InjectionTarget;
+import jakarta.enterprise.inject.spi.ProcessInjectionTarget;
+import jakarta.enterprise.inject.spi.ProcessProducer;
+import jakarta.enterprise.inject.spi.Producer;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class MockExtension implements Extension, Deactivatable
+{
+    private Boolean isActivated = true;
+    private List<MockFilter> mockFilters;
+
+    protected void init(@Observes BeforeBeanDiscovery beforeBeanDiscovery)
+    {
+        isActivated = ClassDeactivationUtils.isActivated(getClass());
+        mockFilters = 
ServiceUtils.loadServiceImplementations(MockFilter.class);
+    }
+
+    public <X> void onProcessInjectionTarget(@Observes 
ProcessInjectionTarget<X> processInjectionTarget,
+                                             BeanManager beanManager)
+    {
+        if (!isActivated)
+        {
+            return;
+        }
+
+        for (MockFilter mockFilter : mockFilters)
+        {
+            if (!mockFilter.isMockedImplementationSupported(beanManager, 
processInjectionTarget.getAnnotatedType()))
+            {
+                return;
+            }
+        }
+
+        List<Annotation> qualifiers = new ArrayList<Annotation>();
+        for (Annotation annotation : 
processInjectionTarget.getAnnotatedType().getAnnotations())
+        {
+            if (beanManager.isQualifier(annotation.annotationType()))
+            {
+                qualifiers.add(annotation);
+            }
+        }
+
+        Typed typed = 
processInjectionTarget.getAnnotatedType().getAnnotation(Typed.class);
+
+        List<Type> foundTypes = new ArrayList<>();
+        if (typed != null)
+        {
+            Collections.addAll(foundTypes, typed.value());
+        }
+        else
+        {
+            
foundTypes.addAll(extractTypes(processInjectionTarget.getAnnotatedType().getJavaClass()));
+        }
+
+        if (foundTypes.isEmpty())
+        {
+            return;
+        }
+
+        final InjectionTarget<X> originalInjectionTarget = 
processInjectionTarget.getInjectionTarget();
+        processInjectionTarget.setInjectionTarget(new 
MockAwareInjectionTargetWrapper<X>(
+            beanManager, originalInjectionTarget, foundTypes, qualifiers));
+    }
+
+    public <X, T> void onProcessProducer(@Observes ProcessProducer<X, T> 
processProducer, BeanManager beanManager)
+    {
+        if (!isActivated)
+        {
+            return;
+        }
+
+        for (MockFilter mockFilter : mockFilters)
+        {
+            if (!mockFilter.isMockedImplementationSupported(beanManager, 
processProducer.getAnnotatedMember()))
+            {
+                return;
+            }
+        }
+
+        final Producer<T> originalProducer = processProducer.getProducer();
+        AnnotatedMember<X> annotatedMember = 
processProducer.getAnnotatedMember();
+        List<Annotation> qualifiers = new ArrayList<Annotation>();
+        for (Annotation annotation : annotatedMember.getAnnotations())
+        {
+            if (beanManager.isQualifier(annotation.annotationType()))
+            {
+                qualifiers.add(annotation);
+            }
+        }
+
+        Typed typed = annotatedMember.getAnnotation(Typed.class);
+
+        List<Type> foundTypes = new ArrayList<Type>();
+        if (typed != null)
+        {
+            Collections.addAll(foundTypes, typed.value());
+        }
+        else if (annotatedMember.getBaseType() instanceof Class)
+        {
+            
foundTypes.addAll(extractTypes((Class)annotatedMember.getBaseType()));
+        }
+
+        if (foundTypes.isEmpty())
+        {
+            return;
+        }
+
+        processProducer.setProducer(new MockAwareProducerWrapper<T>(
+            beanManager, originalProducer, foundTypes, qualifiers));
+    }
+
+    protected List<Type> extractTypes(Class currentClass)
+    {
+        List<Type> result = new ArrayList<Type>();
+        for (Class<?> c = currentClass; c != Object.class && c != null; c = 
c.getSuperclass())
+        {
+            result.add(c);
+        }
+        Collections.addAll(result, currentClass.getInterfaces());
+        return result;
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleApplicationMockManager.java
 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleApplicationMockManager.java
new file mode 100644
index 000000000..d3dd4439a
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleApplicationMockManager.java
@@ -0,0 +1,30 @@
+/*
+ * 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.deltaspike.testcontrol5.impl.mock;
+
+import jakarta.enterprise.inject.Typed;
+import org.apache.deltaspike.testcontrol5.api.mock.ApplicationMockManager;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+@Typed(ApplicationMockManager.class)
+public class SimpleApplicationMockManager extends AbstractMockManager 
implements ApplicationMockManager
+{
+}
diff --git 
a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleMockManager.java
 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleMockManager.java
new file mode 100644
index 000000000..f00699aff
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleMockManager.java
@@ -0,0 +1,47 @@
+/*
+ * 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.deltaspike.testcontrol5.impl.mock;
+
+import jakarta.enterprise.inject.Typed;
+import org.apache.deltaspike.testcontrol5.api.mock.ApplicationMockManager;
+import org.apache.deltaspike.testcontrol5.api.mock.DynamicMockManager;
+
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import java.lang.annotation.Annotation;
+
+@RequestScoped
+@Typed(DynamicMockManager.class)
+public class SimpleMockManager extends AbstractMockManager
+{
+    @Inject
+    private ApplicationMockManager applicationMockManager;
+
+    @Override
+    public <T> T getMock(Class<T> beanClass, Annotation... qualifiers)
+    {
+        T result = applicationMockManager.getMock(beanClass, qualifiers);
+
+        if (result != null)
+        {
+            return result;
+        }
+        return super.getMock(beanClass, qualifiers);
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/request/ContextControlDecorator.java
 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/request/ContextControlDecorator.java
new file mode 100644
index 000000000..32e6882a1
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/request/ContextControlDecorator.java
@@ -0,0 +1,115 @@
+/*
+ * 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.deltaspike.testcontrol5.impl.request;
+
+import org.apache.deltaspike.cdise.api.ContextControl;
+import org.apache.deltaspike.testcontrol5.api.junit.CdiTestExtension;
+import org.apache.deltaspike.testcontrol5.spi.ExternalContainer;
+
+import jakarta.decorator.Decorator;
+import jakarta.decorator.Delegate;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.ConversationScoped;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.enterprise.context.SessionScoped;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import java.lang.annotation.Annotation;
+
+@Decorator
+public class ContextControlDecorator implements ContextControl
+{
+    @Inject
+    @Delegate
+    private ContextControl wrapped;
+
+    @Override
+    public void startContexts()
+    {
+        wrapped.startContexts();
+
+        if (isManualScopeHandling())
+        {
+            for (ExternalContainer externalContainer : 
CdiTestExtension.getActiveExternalContainers())
+            {
+                externalContainer.startScope(Singleton.class);
+                externalContainer.startScope(ApplicationScoped.class);
+                externalContainer.startScope(RequestScoped.class);
+                externalContainer.startScope(SessionScoped.class);
+                externalContainer.startScope(ConversationScoped.class);
+            }
+        }
+    }
+
+    @Override
+    public void stopContexts()
+    {
+        if (isManualScopeHandling())
+        {
+            for (ExternalContainer externalContainer : 
CdiTestExtension.getActiveExternalContainers())
+            {
+                externalContainer.stopScope(ConversationScoped.class);
+                externalContainer.stopScope(SessionScoped.class);
+                externalContainer.stopScope(RequestScoped.class);
+                externalContainer.stopScope(ApplicationScoped.class);
+                externalContainer.stopScope(Singleton.class);
+            }
+        }
+
+        wrapped.stopContexts();
+    }
+
+    @Override
+    public void startContext(Class<? extends Annotation> scopeClass)
+    {
+        wrapped.startContext(scopeClass);
+
+        if (isManuallyHandledRequest(scopeClass))
+        {
+            for (ExternalContainer externalContainer : 
CdiTestExtension.getActiveExternalContainers())
+            {
+                externalContainer.startScope(scopeClass);
+            }
+        }
+    }
+
+    @Override
+    public void stopContext(Class<? extends Annotation> scopeClass)
+    {
+        wrapped.stopContext(scopeClass);
+
+        if (isManuallyHandledRequest(scopeClass))
+        {
+            for (ExternalContainer externalContainer : 
CdiTestExtension.getActiveExternalContainers())
+            {
+                externalContainer.stopScope(scopeClass);
+            }
+        }
+    }
+
+    private boolean isManuallyHandledRequest(Class<? extends Annotation> 
scopeClass)
+    {
+        return RequestScoped.class.equals(scopeClass) && 
isManualScopeHandling();
+    }
+
+    private boolean isManualScopeHandling()
+    {
+        return 
!Boolean.TRUE.equals(CdiTestExtension.isAutomaticScopeHandlingActive());
+    }
+}
diff --git 
a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/validation/StandardContextTestControlValidator.java
 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/validation/StandardContextTestControlValidator.java
new file mode 100644
index 000000000..35461e9a6
--- /dev/null
+++ 
b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/validation/StandardContextTestControlValidator.java
@@ -0,0 +1,107 @@
+/*
+ * 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.deltaspike.testcontrol5.impl.validation;
+
+import org.apache.deltaspike.cdise.api.CdiContainerLoader;
+import org.apache.deltaspike.testcontrol5.api.TestControl;
+import org.apache.deltaspike.testcontrol5.spi.TestAware;
+import org.apache.deltaspike.testcontrol5.spi.TestControlValidator;
+
+import jakarta.enterprise.inject.Vetoed;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Vetoed
+public class StandardContextTestControlValidator implements TestAware, 
TestControlValidator
+{
+    private static Boolean customContextControlDetected;
+
+    private static ThreadLocal<Class> currentTestClass = new 
ThreadLocal<Class>();
+    private static ThreadLocal<Method> currentTestMethod = new 
ThreadLocal<Method>();
+
+    @Override
+    public void validate(TestControl testControl)
+    {
+        checkActiveContextControlImplementation();
+
+        List<Class<? extends Annotation>> scopeClasses = new ArrayList<Class<? 
extends Annotation>>();
+        Collections.addAll(scopeClasses, testControl.startScopes());
+
+        validateSupportedScopes(scopeClasses, currentTestClass.get(), 
currentTestMethod.get());
+    }
+
+    private void checkActiveContextControlImplementation()
+    {
+        if (customContextControlDetected != null)
+        {
+            return;
+        }
+
+        customContextControlDetected = 
!CdiContainerLoader.getCdiContainer().getContextControl()
+            .getClass().getName().startsWith("org.apache.deltaspike.");
+    }
+
+    private void validateSupportedScopes(List<Class<? extends Annotation>> 
scopeClasses,
+                                         Class<?> declaringClass,
+                                         Method testMethod)
+    {
+        if (Boolean.TRUE.equals(customContextControlDetected))
+        {
+            return;
+        }
+
+        for (Class<? extends Annotation> scopeClass : scopeClasses)
+        {
+            if 
(!scopeClass.getName().startsWith("jakarta.enterprise.context."))
+            {
+                throw new IllegalStateException("Please remove " + 
scopeClass.getName() + " at " + declaringClass +
+                        (testMethod != null ? "#" + testMethod.getName() : "") 
+
+                        " from @" + TestControl.class.getName() + ". @" + 
TestControl.class.getName() +
+                        " only supports standard Scope-Annotations provided by 
the CDI-Specification. " +
+                        "Other Contexts start automatically or need to get 
started with a specific Management-API. " +
+                        "Examples: " +
+                        "@TransactionScoped gets started automatically once 
the @Transactional-Interceptor is used. " +
+                        "Whereas @WindowScoped starts once 
WindowContext#activateWindow gets called.");
+            }
+        }
+    }
+
+    @Override
+    public void setTestClass(Class testClass)
+    {
+        currentTestClass.set(testClass);
+        if (testClass == null)
+        {
+            currentTestClass.remove();
+        }
+    }
+
+    @Override
+    public void setTestMethod(Method testMethod)
+    {
+        currentTestMethod.set(testMethod);
+        if (testMethod == null)
+        {
+            currentTestMethod.remove();
+        }
+    }
+}
diff --git a/deltaspike/modules/pom.xml 
b/deltaspike/modules/test-control5/pom.xml
similarity index 70%
copy from deltaspike/modules/pom.xml
copy to deltaspike/modules/test-control5/pom.xml
index d4017033f..7d230af65 100644
--- a/deltaspike/modules/pom.xml
+++ b/deltaspike/modules/test-control5/pom.xml
@@ -21,27 +21,20 @@
     <modelVersion>4.0.0</modelVersion>
 
     <parent>
-        <groupId>org.apache.deltaspike</groupId>
-        <artifactId>parent-code</artifactId>
+        <groupId>org.apache.deltaspike.modules</groupId>
+        <artifactId>modules-project</artifactId>
         <version>2.0.2-SNAPSHOT</version>
-        <relativePath>../parent/code/pom.xml</relativePath>
+        <relativePath>../pom.xml</relativePath>
     </parent>
 
     <groupId>org.apache.deltaspike.modules</groupId>
-    <artifactId>modules-project</artifactId>
-    <version>2.0.2-SNAPSHOT</version>
+    <artifactId>test-control5-module-project</artifactId>
     <packaging>pom</packaging>
 
-    <name>Apache DeltaSpike Modules</name>
+    <name>Apache DeltaSpike Test-Control5-Module (JUnit 5)</name>
 
     <modules>
-        <module>proxy</module>
-        <module>security</module>
-        <module>jsf</module>
-        <module>partial-bean</module>
-        <module>jpa</module>
-        <module>data</module>
-        <module>scheduler</module>
-        <module>test-control</module>
+        <module>api</module>
+        <module>impl</module>
     </modules>
 </project>

Reply via email to