CDI SE and JUnit 5 made easy
CDI 2.0 brings to us a standard standalone API. It enables to start, stop the container, lookup beans etc.
If you never played with it here what it looks like:
try (final SeContainer container = SeContainerInitializer.newInstance() // 1
.disableDiscovery() // 2
.addBeanClasses(MyEntryPoint.class) // 2
.initialize()) { // 3
container.select(MyEntryPoint.class).get().run(); // 4
}
- The SeContainerInitializer provides a way to configure the container before its startup,
- Several methods enables to customize what is deployed (classes, extensions, interceptors, ....) - default being classpath scanning,
- Initialize starts the configured container,
- The SeContainer is itself an Instance which enables to lookup any bean through select method, it also provides the BeanManager if needed.
This is very flexible and usable in tests:
class MyServiceTest {
@Test
void test() {
try (final SeContainer container = SeContainerInitializer.newInstance()
.disableDiscovery()
.addBeanClasses(MyService.class)
.initialize()) {
final MyService service = container.select(MyService.class).get();
// asserts
}
}
}
However it is quite verbose and the test code will often take less space than the technical glue code to start the container.
To solve that Apache OpenWebBeans created a JUnit 5 extension which will be available in its 2.0.11 version. It defines a @Cdi annotation which is a JUnit 5 extension which enable to map the SeContainerInitializer methods. Here is a typical test with this API:
@Cdi(disableDiscovery = true, classes = MyService.class)
class CdiTest {
@Inject
private MyService service;
@Test
void test() {
assertEquals("ok", service.ok());
}
}
There are two immediate enhancement compared to previous test:
- The container setup code is just one line (and parameter are optional if you want a classpath deployment),
- The beans can be injected as we are used to with alternatives (Arquillian, Meecrowave, TomEE ApplicationComposer, DeltaSpike, etc...).
This makes tests fast - you can minimize what you deploy, flexible - you can add alternatives or specialization to mock part of your system, and simple.
However OpenWebBeans does not stop there and also add a parameter to @Cdi which is not in the initializer API: reusable. This parameter will enable to reuse the previous container if existing for current test avoiding the start/stop phases to be repeated again and again when executing multiple tests against the same deployment.
The issue with such feature is how to ensure all the reusable tests are sharing the same configuration - indeed a difference would break one test if it misses a bean or extension. JUnit 5 answers to that point providing meta annotation support for its extensions. In other words you can create your own annotation decorated with a @Cdi pre-configured:
@Target(TYPE)
@Retention(RUNTIME)
@Cdi(reusable = true, disableDiscovery = true, packages = MyService.class)
public @interface TestConfig {
}
And now you can just use that instead of @Cdi:
@TestConfig
class MySecondTest {
// injects, tests as before
}
With some surefire - or you build tool test solution - tuning you can mix both options and even mix standalone and web tests since Apache Meecrowave implements this API as well so this can be a replacement of @MeecrowaveConfig! This is really powerful if you write CDI applications.
If you are interested into this feature, there is some documentation on openwebbeans website on the junit5 page.
From the same author:
In the same category: