Wiring ratchet
Every Spring configuration class in a Java module (a class annotated with @Configuration,
@SpringBootApplication, @AutoConfiguration, ..., or any concrete class that declares @Bean
methods) must be loaded by a real-context test in the same module, or be listed in that
module's wiring-baseline.txt. The baseline may only shrink: the check fails on entries that are
no longer needed, and adding an entry is a reviewed exception (the build cannot tell a new entry
from an old one).
A wiring bug (a missing @Import, a wrong @Conditional, a constructor that throws with
production config) only shows up when Spring actually runs the @Bean method. Unit tests that
call new FooFactory().createFoo(...), and Spring tests that replace the bean with
@MockitoBean, never do.
How it works
- Recording. Every Java module except
datahub-frontend(Play/Guice) gets:wiring-coverageon its test classpath. ThroughMETA-INF/spring.factoriesit attaches aBeanPostProcessorto every context built by the Spring TestContext framework (@ContextConfiguration,AbstractTestNGSpringContextTests,@SpringBootTest) and bySpringApplication. It writes tobuild/wiring-coverage/test/which configuration classes were instantiated and which@Beanmethods really ran. Beans replaced with@MockitoBeannever reach their@Beanmethod, so they do not count. - Checking.
testis finalized bycheckWiringCoverage, which scans the module's compiled main classes and compares them against the recording andwiring-baseline.txt.
A configuration class is covered when at least one of its own (not inherited) @Bean methods
ran for real in a test context, or, for a class with no @Bean methods, when a test context
instantiated it. The report
in build/reports/wiring-coverage/report.txt also lists @Bean methods of covered classes that
no test ran.
Coverage is per module: a factory in metadata-service/factories loaded only by a
metadata-service/war test does not count. This keeps the check deterministic when CI shards
tests by module.
The check is skipped when the test task failed or ran with --tests (a partial run). It adds a
class-file scan (well under a second) to each module's test run.
A module with no test sources has nothing to record, so its check runs from check without a test
run, and every configuration class it has must be baselined. Custom annotations meta-annotated with
@Configuration are only recognised when declared in the same module; classes using one from
another module are found only if they declare @Bean methods.
When the check fails
New uncovered class: add a Spring test that loads it and lets at least one of its
@Beanmethods run (or, if it has none, that instantiates it), mocking its dependencies rather than its beans. Contexts built by hand must opt in:new ApplicationContextRunner()
.withInitializer(WiringCoverage.initializer())
.withUserConfiguration(MyFactory.class)
...
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
WiringCoverage.attach(ctx);Adding the class to
wiring-baseline.txtis a last resort, for reviewers to judge.Baselined class is now covered (or was deleted): remove it from
wiring-baseline.txt, or run./gradlew :module:test -PupdateWiringBaseline, which only prunes.