diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc
index e2c0fac89ccbe..35c28cc97071a 100644
--- a/docs/src/main/asciidoc/getting-started-testing.adoc
+++ b/docs/src/main/asciidoc/getting-started-testing.adoc
@@ -369,6 +369,8 @@ public class GreetingServiceTest {
 ----
 <1> The `GreetingService` bean will be injected into the test
 
+TIP: If you want to inject/test a `@SessionScoped` bean then it's very likely that the session context is not active and you would receive the `ContextNotActiveException` when a method of the injected bean is invoked. However, it's possible to use the `@io.quarkus.test.ActivateSessionContext` interceptor binding to activate the session context for a specific business method. Please read the javadoc for futher limitations.
+
 == Applying Interceptors to Tests
 
 As mentioned above Quarkus tests are actually full CDI beans, and as such you can apply CDI interceptors as you would
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
index ff175046a6960..a89b988cb9184 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
@@ -68,16 +68,13 @@
 import io.quarkus.arc.runtime.LoggerProducer;
 import io.quarkus.arc.runtime.appcds.AppCDSRecorder;
 import io.quarkus.arc.runtime.context.ArcContextProvider;
-import io.quarkus.arc.runtime.test.PreloadedTestApplicationClassPredicate;
 import io.quarkus.bootstrap.BootstrapDebug;
 import io.quarkus.deployment.Capabilities;
 import io.quarkus.deployment.Capability;
 import io.quarkus.deployment.Feature;
-import io.quarkus.deployment.IsTest;
 import io.quarkus.deployment.annotations.BuildProducer;
 import io.quarkus.deployment.annotations.BuildStep;
 import io.quarkus.deployment.annotations.Consume;
-import io.quarkus.deployment.annotations.ExecutionTime;
 import io.quarkus.deployment.annotations.Produce;
 import io.quarkus.deployment.annotations.Record;
 import io.quarkus.deployment.builditem.AdditionalApplicationArchiveMarkerBuildItem;
@@ -653,27 +650,6 @@ public void signalBeanContainerReady(AppCDSRecorder recorder, PreBeanContainerBu
         beanContainerProducer.produce(new BeanContainerBuildItem(bi.getValue()));
     }
 
-    @BuildStep(onlyIf = IsTest.class)
-    public AdditionalBeanBuildItem testApplicationClassPredicateBean() {
-        // We need to register the bean implementation for TestApplicationClassPredicate
-        // TestApplicationClassPredicate is used programmatically in the ArC recorder when StartupEvent is fired
-        return AdditionalBeanBuildItem.unremovableOf(PreloadedTestApplicationClassPredicate.class);
-    }
-
-    @BuildStep(onlyIf = IsTest.class)
-    @Record(ExecutionTime.STATIC_INIT)
-    void initTestApplicationClassPredicateBean(ArcRecorder recorder, BeanContainerBuildItem beanContainer,
-            BeanDiscoveryFinishedBuildItem beanDiscoveryFinished,
-            CompletedApplicationClassPredicateBuildItem predicate) {
-        Set<String> applicationBeanClasses = new HashSet<>();
-        for (BeanInfo bean : beanDiscoveryFinished.beanStream().classBeans()) {
-            if (predicate.test(bean.getBeanClass())) {
-                applicationBeanClasses.add(bean.getBeanClass().toString());
-            }
-        }
-        recorder.initTestApplicationClassPredicate(applicationBeanClasses);
-    }
-
     @BuildStep
     List<AdditionalApplicationArchiveMarkerBuildItem> marker() {
         return Arrays.asList(new AdditionalApplicationArchiveMarkerBuildItem("META-INF/beans.xml"),
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestSteps.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestSteps.java
new file mode 100644
index 0000000000000..66c86e0b055e5
--- /dev/null
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestSteps.java
@@ -0,0 +1,71 @@
+package io.quarkus.arc.deployment;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationTransformation;
+import org.jboss.jandex.DotName;
+
+import io.quarkus.arc.processor.BeanInfo;
+import io.quarkus.arc.runtime.ArcRecorder;
+import io.quarkus.arc.runtime.test.ActivateSessionContextInterceptor;
+import io.quarkus.arc.runtime.test.PreloadedTestApplicationClassPredicate;
+import io.quarkus.deployment.IsTest;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.BuildSteps;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem;
+
+@BuildSteps(onlyIf = IsTest.class)
+public class ArcTestSteps {
+
+    @BuildStep
+    public void additionalBeans(BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
+        // We need to register the bean implementation for TestApplicationClassPredicate
+        // TestApplicationClassPredicate is used programmatically in the ArC recorder when StartupEvent is fired
+        additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(PreloadedTestApplicationClassPredicate.class));
+        // In tests, register the ActivateSessionContextInterceptor and ActivateSessionContext interceptor binding
+        additionalBeans.produce(new AdditionalBeanBuildItem(ActivateSessionContextInterceptor.class));
+        additionalBeans.produce(new AdditionalBeanBuildItem("io.quarkus.test.ActivateSessionContext"));
+    }
+
+    @BuildStep
+    AnnotationsTransformerBuildItem addInterceptorBinding() {
+        return new AnnotationsTransformerBuildItem(
+                AnnotationTransformation.forClasses().whenClass(ActivateSessionContextInterceptor.class).transform(tc -> tc.add(
+                        AnnotationInstance.builder(DotName.createSimple("io.quarkus.test.ActivateSessionContext")).build())));
+    }
+
+    // For some reason the annotation literal generated for io.quarkus.test.ActivateSessionContext lives in app class loader.
+    // This predicates ensures that the generated bean is considered an app class too.
+    // As a consequence, the type and all methods of ActivateSessionContextInterceptor must be public.
+    @BuildStep
+    ApplicationClassPredicateBuildItem appClassPredicate() {
+        return new ApplicationClassPredicateBuildItem(new Predicate<String>() {
+
+            @Override
+            public boolean test(String name) {
+                return name.startsWith(ActivateSessionContextInterceptor.class.getName());
+            }
+        });
+    }
+
+    @BuildStep
+    @Record(ExecutionTime.STATIC_INIT)
+    void initTestApplicationClassPredicateBean(ArcRecorder recorder, BeanContainerBuildItem beanContainer,
+            BeanDiscoveryFinishedBuildItem beanDiscoveryFinished,
+            CompletedApplicationClassPredicateBuildItem predicate) {
+        Set<String> applicationBeanClasses = new HashSet<>();
+        for (BeanInfo bean : beanDiscoveryFinished.beanStream().classBeans()) {
+            if (predicate.test(bean.getBeanClass())) {
+                applicationBeanClasses.add(bean.getBeanClass().toString());
+            }
+        }
+        recorder.initTestApplicationClassPredicate(applicationBeanClasses);
+    }
+
+}
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/Client.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/Client.java
new file mode 100644
index 0000000000000..358756bab8210
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/Client.java
@@ -0,0 +1,32 @@
+package io.quarkus.arc.test.context.session;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import jakarta.enterprise.context.Dependent;
+import jakarta.enterprise.context.SessionScoped;
+import jakarta.inject.Inject;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ClientProxy;
+import io.quarkus.test.ActivateSessionContext;
+
+@Dependent
+class Client {
+
+    @Inject
+    SimpleBean bean;
+
+    @ActivateSessionContext
+    public boolean ping() {
+        assertTrue(Arc.container().sessionContext().isActive());
+        if (bean instanceof ClientProxy proxy) {
+            assertEquals(SessionScoped.class, proxy.arc_bean().getScope());
+        } else {
+            fail("Not a client proxy");
+        }
+        return bean.ping();
+    }
+
+}
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SessionContextTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SessionContextTest.java
new file mode 100644
index 0000000000000..74ed4d9475fc3
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SessionContextTest.java
@@ -0,0 +1,31 @@
+package io.quarkus.arc.test.context.session;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class SessionContextTest {
+
+    @RegisterExtension
+    static final QuarkusUnitTest config = new QuarkusUnitTest()
+            .withApplicationRoot(root -> root
+                    .addClasses(SimpleBean.class, Client.class));
+
+    @Inject
+    Client client;
+
+    @Test
+    public void testContexts() {
+        assertFalse(Arc.container().sessionContext().isActive());
+        assertTrue(client.ping());
+        assertTrue(SimpleBean.DESTROYED.get());
+        assertFalse(Arc.container().sessionContext().isActive());
+    }
+}
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SimpleBean.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SimpleBean.java
new file mode 100644
index 0000000000000..47e2c73e3ff26
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SimpleBean.java
@@ -0,0 +1,21 @@
+package io.quarkus.arc.test.context.session;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jakarta.annotation.PreDestroy;
+import jakarta.enterprise.context.SessionScoped;
+
+@SessionScoped
+class SimpleBean {
+
+    static final AtomicBoolean DESTROYED = new AtomicBoolean();
+
+    public boolean ping() {
+        return true;
+    }
+
+    @PreDestroy
+    void destroy() {
+        DESTROYED.set(true);
+    }
+}
\ No newline at end of file
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/test/ActivateSessionContextInterceptor.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/test/ActivateSessionContextInterceptor.java
new file mode 100644
index 0000000000000..ea5452e2babc7
--- /dev/null
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/test/ActivateSessionContextInterceptor.java
@@ -0,0 +1,30 @@
+package io.quarkus.arc.runtime.test;
+
+import jakarta.annotation.Priority;
+import jakarta.interceptor.AroundInvoke;
+import jakarta.interceptor.Interceptor;
+import jakarta.interceptor.InvocationContext;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ManagedContext;
+
+// The @ActivateSessionContext interceptor binding is added by the extension
+@Interceptor
+@Priority(Interceptor.Priority.PLATFORM_BEFORE + 100)
+public class ActivateSessionContextInterceptor {
+
+    @AroundInvoke
+    public Object aroundInvoke(InvocationContext ctx) throws Exception {
+        ManagedContext sessionContext = Arc.container().sessionContext();
+        if (sessionContext.isActive()) {
+            return ctx.proceed();
+        }
+        try {
+            sessionContext.activate();
+            return ctx.proceed();
+        } finally {
+            sessionContext.terminate();
+        }
+    }
+
+}
diff --git a/test-framework/common/src/main/java/io/quarkus/test/ActivateSessionContext.java b/test-framework/common/src/main/java/io/quarkus/test/ActivateSessionContext.java
new file mode 100644
index 0000000000000..0768213c6b1d5
--- /dev/null
+++ b/test-framework/common/src/main/java/io/quarkus/test/ActivateSessionContext.java
@@ -0,0 +1,30 @@
+package io.quarkus.test;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.concurrent.CompletionStage;
+
+import jakarta.interceptor.InterceptorBinding;
+
+/**
+ * Activates the session context before the intercepted method is called, and terminates the context when the method invocation
+ * completes (regardless of any exceptions being thrown).
+ * <p>
+ * If the context is already active, it's a noop - the context is neither activated nor deactivated.
+ * <p>
+ * Keep in mind that if the method returns an asynchronous type (such as {@link CompletionStage} then the session context is
+ * still terminate when the invocation completes and not at the time the asynchronous type is completed. Also note that session
+ * context is not propagated by MicroProfile Context Propagation.
+ * <p>
+ * This interceptor binding is only available in tests.
+ */
+@InterceptorBinding
+@Target({ METHOD, TYPE })
+@Retention(RUNTIME)
+public @interface ActivateSessionContext {
+
+}