Auto-Mocking: CDI Lifecycle Hooks
The extension uses three CDI lifecycle events to discover unsatisfied injection points and fill them with Mockito mocks.
1. Collecting injection points
<T> void collectInjectionPoints(@Observes ProcessInjectionPoint<?, T> pip) {
// Records the type and qualifiers of every injection point
}
<T> void collectFromBean(@Observes ProcessBean<T> pb) {
// Catches inherited generic injection points that some
// implementations miss in ProcessInjectionPoint
}
The ProcessBean observer is critical for
OpenWebBeans compatibility — OWB does not always
fire ProcessInjectionPoint for inherited generic fields
like BaseDao<E> in an abstract superclass.
2. Registering beans and mocks
During AfterBeanDiscovery, the extension registers (in order):
- Test class as a
@Singletonbean (ifaddTestClass = true) - Inline
@Produces @TestBeanfields from the active test class — static field values become@Singletonbeans - Mockito mocks for remaining unsatisfied injection
points (skipping types already covered by inline fields,
@TestBeanalternatives, or third-party extensions)
MockBean properties
PassivationCapable
Unique ID per bean type, satisfying Weld's requirement for normal-scoped beans.
Qualifier-Aware
Carries exact qualifiers from the injection point. Follows CDI spec rules for @Default.
@Nonbinding Support
Qualifier comparison uses reflection to skip @Nonbinding members.
@Named Support
Returns the @Named value as the bean name when present.
@TestBean: Alternative Isolation
When @EnableTestBeans is present, the extension adds three
more CDI lifecycle phases before auto-mocking:
Phase -2: Force discovery of scope-less alternatives
During BeforeBeanDiscovery, if a @TestBean-referenced
class has @Alternative but no scope annotation, the extension
calls addAnnotatedType() to force CDI to discover it in
bean-discovery-mode="annotated", then adds @Dependent
as the default scope.
Phase -1: Veto conflicting alternatives
During ProcessAnnotatedType, the extension checks each
@Alternative bean against the @TestBean set.
Bean types are resolved respecting @Typed on both the
candidate and the selected alternatives:
- Selected by @TestBean — kept. If it has no
scope,
@Dependentis added. - Not selected, bean types clash with a
@TestBean-declared alternative — vetoed (regardless of@Priority). - Not selected, no type clash — left alone.
This includes alternatives enabled via
@Priorityorbeans.xmlfor unrelated types. - No active test class — alternatives without
@Priorityare vetoed (inactive anyway, prevents producer conflicts).@Priorityalternatives are untouched.
In whitelist mode (limitToTestBeans = true), ALL
application beans are vetoed except the whitelisted ones.
Phase 0: Enable selected alternatives
During AfterTypeDiscovery, the extension reads
@TestBean annotations from the active test class —
recursively walking meta-annotations — and enables each referenced
alternative via getAlternatives().add().
@Priority
or beans.xml — work normally as long as their
bean types don't overlap with a @TestBean replacement.
JUnit Integration
@EnableTestBeans carries @ExtendWith for the
internal JUnit extension. The lifecycle:
| JUnit Phase | Action |
|---|---|
beforeAll |
Sets active test class in DynamicTestBeanContext.
If manageContainer = true, starts the CDI SE container. |
beforeEach |
Activates request scope via RequestContextController. |
postProcessTestInstance |
Injects @Inject fields on the test instance using
BeanManager.getInjectionTargetFactory(). |
afterEach |
Deactivates request scope. |
afterAll |
Shuts down the CDI container. Resets DynamicTestBeanContext. |
Meta-Annotation Resolution
@TestBean declarations are collected recursively from the
test class's annotation hierarchy. The algorithm:
- Read all annotations on the test class
- For each
@TestBeanor@TestBeans, collect thebeanandbeanProducerreferences - For each non-JDK/non-CDI annotation, recurse into its meta-annotations
- Track visited annotation types to prevent infinite loops
- Collect into a
Set<Class<?>>— duplicates are automatically deduplicated
Third-Party Extension Compatibility
The extension detects types managed by third-party CDI extensions and skips mocking them. Currently recognized:
- Apache DeltaSpike — types whose annotations
carry
@PartialBeanBindingare skipped. Detection uses annotation name strings to avoid compile-time dependencies.
Package Structure
| Package | Contents |
|---|---|
org.os890.cdi.addon.dynamictestbean |
Public API — @EnableTestBeans,
@TestBean, @TestBeans |
org.os890.cdi.addon.dynamictestbean.internal |
Extension internals — DynamicTestBeanExtension,
DynamicTestBeanJUnitExtension,
DynamicTestBeanContext |