The Challenge
The addon uses six CDI lifecycle events —
BeforeBeanDiscovery, ProcessAnnotatedType,
AfterTypeDiscovery, ProcessInjectionPoint,
ProcessBean, and AfterBeanDiscovery —
to discover unsatisfied injection points and register Mockito mocks.
ArC fires none of these at runtime. It processes everything at build
time through a BeanProcessor pipeline.
CDI SE vs ArC: API Mapping
| Feature | CDI SE | Quarkus/ArC |
|---|---|---|
| Bean discovery | Runtime classpath scanning | Jandex index at build time |
| Synthetic beans | AfterBeanDiscovery.addBean() | BeanRegistrar + BeanConfigurator |
| Alternatives | AfterTypeDiscovery.getAlternatives() | AnnotationTransformation — add @Priority |
| Vetoing beans | ProcessAnnotatedType.veto() | BeanProcessor.addExcludeType() |
| Container boot | SeContainerInitializer.initialize() | BeanProcessor.process() + Arc.initialize() |
| Mock creation | Custom Bean<T> impl | BeanCreator<T> — Mockito.mock() |
| Test injection | InjectionTarget.inject() | ArcContainer.select().get() |
| Request scope | RequestContextController | Arc.container().requestContext() |
The Approach
We use ArC's processor APIs directly — the same way ArC's own
ArcTestContainer and Quarkus's
QuarkusComponentTestExtension do it. No
@BuildStep processors, no multi-module deployment/runtime
split. @QuarkusTest is not required — but if present,
the addon detects it and skips container management, letting the
Quarkus test framework handle the lifecycle.
The result is a single JUnit 5 extension that performs a full ArC build
pass inside @BeforeAll:
@EnableTestBeans
@TestBean(bean = CustomGreeting.class)
class MyTest {
@Inject
GreetingConsumer consumer;
@Test
void greetingIsReplaced() {
assertEquals("Hello, world!", consumer.getGreeting().greet("world"));
}
}
Same annotations. Same test structure. Same developer experience.
How It Works
- Discover classes — scans test-classes directory and classpath JARs, filters out other test classes
- Build Jandex index — indexes every bean class and its type hierarchy
- Transform annotations — adds
@Priority(MAX_VALUE)to@TestBean-selected alternatives - Exclude clashing beans — unselected alternatives with type overlap are excluded
- Register mock beans — a
BeanRegistrariterates all injection points, unsatisfied ones get aMockBeanCreator - Register inline fields —
@TestBeanstatic fields get anInlineFieldBeanCreator - Process and boot —
BeanProcessor.process()generates bytecode,Arc.initialize()starts the container
@Nonbinding — The Subtle One
CDI qualifiers can have @Nonbinding members — meaning
those values are ignored during bean resolution. If two injection points
differ only in a @Nonbinding member, they must share the
same mock bean. The ArC version queries the BeanDeployment
for nonbinding members, strips those values from qualifier annotations
before registration, and deduplicates using a key that ignores
nonbinding member values.
What Was Eliminated
| CDI SE Component | Quarkus Status |
|---|---|
| DynamicTestBeanExtension | Replaced by BeanRegistrar + AnnotationTransformation |
| DynamicTestBeanContext | Eliminated — no static volatile bridge needed |
| META-INF/services/...Extension | Eliminated — @ExtendWith is the entry point |
| Weld / OWB profiles | Eliminated — ArC only |
| manageContainer flag | Ignored — container managed by the extension (or by @QuarkusTest if present) |
Using with @QuarkusTest
The addon works standalone — it boots and manages its own ArC
container. But in a full Quarkus application you may already use
@QuarkusTest, which manages the container itself. Using
both is safe: when @QuarkusTest (or
@QuarkusComponentTest) is detected on the test class, the
addon automatically skips all container lifecycle management. Only the
@TestBean / @EnableTestBeans annotations are
processed — the Quarkus test framework handles the rest.
Standalone (addon manages the container)
@EnableTestBeans
@TestBean(bean = CustomGreeting.class)
class StandaloneTest {
@Inject
GreetingConsumer consumer;
@Test
void test() {
assertEquals("Hello, world!", consumer.getGreeting().greet("world"));
}
}
Combined with @QuarkusTest (Quarkus manages the container)
@QuarkusTest // Quarkus manages the container
@EnableTestBeans // addon skips container, provides @TestBean support
@TestBean(bean = CustomGreeting.class)
class QuarkusIntegrationTest {
@Inject
GreetingConsumer consumer;
@Test
void test() {
assertEquals("Hello, world!", consumer.getGreeting().greet("world"));
}
}
@QuarkusTest and @QuarkusComponentTest by
annotation name — no compile-time dependency on the Quarkus test
framework is required. If neither annotation is present, the addon
manages the ArC container itself.
The Result
Maven Dependency
<dependency>
<groupId>org.os890.cdi.addon</groupId>
<artifactId>dynamic-cdi-test-bean-addon-quarkus</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
@EnableTestBeans,
@TestBean) is included transitively. The dependency
footprint is just ArC (arc-processor +
arc-runtime), plus JUnit and Mockito as
provided scope. No quarkus-core, no quarkus-bootstrap,
no SmallRye Config.