The Problem
Multi-module CDI projects fail to start in isolated module tests:
at injection point [BackedAnnotatedField] @Inject private BaseService.dao
Your module injects interfaces or abstract types whose implementations live in a different module. Production assembles everything. Your isolated module test doesn't.
The Fix
-
Add one test dependency:
<dependency> <groupId>org.os890.cdi.addon</groupId> <artifactId>dynamic-cdi-test-bean-addon-cdi</artifactId> <version>1.0.0-SNAPSHOT</version> <scope>test</scope> </dependency>The API (
@EnableTestBeans,@TestBean) is included transitively. Using Quarkus/ArC instead? See the Quarkus page for the matching artifact. -
A typical bean in your common module:
// common/src/main/java @ApplicationScoped public class BaseService<E extends BaseEntity> { @Inject private BaseDao<E> dao; // abstract — impls in app modules @Inject private UserContext userContext; // interface — impl in auth module public boolean isReady() { return dao != null; } public String getEntityName() { // ... derived from E } public E findById(long id) { return dao.findById(id); } }Without the addon, both injection points fail:
BaseDao<E>— abstract class, concrete implementations exist only in app modules.
UserContext— interface defined incommon, but the implementation lives in theauthmodule.
The addon auto-mocks both so the container starts. -
Test it — without a database or auth module:
// common/src/test/java @EnableTestBeans class BaseServiceTest { @Inject BaseService<DummyEntity> service; // real bean in common @Test void serviceStartsWithoutAppModules() { // BaseService is a real common-module bean. // BaseDao and UserContext have no impl here — // the extension auto-mocked both with Mockito. assertNotNull(service); assertTrue(service.isReady()); } @Test void serviceLogicWorksWithoutDao() { // Logic that doesn't touch the DAO works normally. assertEquals("DummyEntity", service.getEntityName()); } @Test void daoCallsReturnMockitoDefaults() { // Calls that go through the mocked DAO return null. assertNull(service.findById(1L)); } }
BaseService<E> is a real bean in common.
It injects BaseDao<E> (abstract — concrete
implementations live in app modules) and UserContext
(just an interface — the auth module provides the
implementation). Both are auto-mocked with Mockito so you can test
the service's own logic in isolation. No @BeforeAll, no
SeContainerInitializer, no manual mock wiring.
Need a real UserContext instead of a mock?
See @TestBean.
[DynamicTestBean] Registering mock for: UserContext
[DynamicTestBean] Registered 2 mock bean(s) for unsatisfied injection points.
What's Next
Auto-mocking gets the container started. When you need more control:
- @TestBean —
replace specific mocks with hand-crafted
@Alternativebeans, per test class - Advanced Features — inline producer fields, whitelist mode, composable meta-annotations
- Internals — CDI lifecycle hooks, JUnit integration, implementation details
.claude/skills/cdi-test/SKILL.md in your project and
use /cdi-test to generate CDI tests with auto-mocking
and @TestBean support.