Weld JUnit 4 extension uses the original JUnit extension system via @Rule
or @ClassRule
annotation.
It requires JUnit 4.9+ and Java 17.
<dependency>
<groupId>org.jboss.weld</groupId>
<artifactId>weld-junit4</artifactId>
<version>${version.weld-junit}</version>
</dependency>
org.jboss.weld.junit4.WeldInitiator
is a TestRule
(JUnit 4.9+) which allows to start/stop
a Weld container per test method execution if used as @Rule
or per test class if used as @ClassRule
.
The container is configured through a provided org.jboss.weld.environment.se.Weld
instance.
By default, the container is optimized for testing purposes, i.e. with automatic discovery and concurrent deployment disabled (see also WeldInitiator.createWeld()
).
However, it is possible to provide a customized Weld
instance - see also WeldInitiator.of(Weld)
and WeldInitiator.from(Weld)
methods.
WeldInitiator
also implements jakarta.enterprise.inject.Instance
and therefore might be used to perform programmatic lookup of bean instances.
Unlike when using Arquillian Weld embedded container, bean archive isolation is enabled by default.
This behaviour can be changed by setting a system property org.jboss.weld.se.archive.isolation
to false
or through the Weld.property()
method.
In that case, Weld will use a "flat" deployment structure - all bean classes share the same bean archive and all beans.xml descriptors are automatically merged into one.
Thus alternatives, interceptors and decorators selected/enabled for a bean archive will be enabled for the whole application.
A convenient static method WeldInitiator.of(Class<?>...)
is also provided - in this case, the container is optimized for testing purposes and only the given bean classes are considered.
import static org.junit.Assert.assertEquals;
import org.junit.Rule;
import org.junit.Test;
class SimpleTest {
@Rule
public WeldInitiator weld = WeldInitiator.of(Foo.class);
@Test
public void testFoo() {
// Note that Weld container is started automatically
// WeldInitiator can be used to perform programmatic lookup of beans
assertEquals("baz", weld.select(Foo.class).get().getBaz());
// WeldInitiator can be used to fire a CDI event
weld.event().select(Baz.class).fire(new Baz());
}
}
It's also possible to use WeldInitiator.ofTestPackage()
- the container is optimized for testing purposes and all the classes from the test class package are added automatically.
class AnotherSimpleTest {
@Rule
public WeldInitiator weld = WeldInitiator.ofTestPackage();
@Test
public void testFoo() {
// Alpha comes from the same package as AnotherSimpleTest
assertEquals(1, weld.select(Alpha.class).ping());
}
}
Furthermore, WeldInitiator.Builder
can be used to customize the final WeldInitiator
instance, e.g. to activate a context for a given normal scope or to inject the test class.
Sometimes, the programmatic lookup can imply unnecessary overhead, e.g. an annotation literal must be used for parameterized types and qualifiers with members.
WeldInitiator.Builder.inject(Object)
instructs the rule to inject the given non-contextual instance once the container is started, i.e. during each test method execution:
class InjectTest {
@Rule
public WeldInitiator weld = WeldInitiator.from(Foo.class).inject(this).build();
// Gets injected by WeldInitiator when testFoo() is about to be run
@Inject
@MyQualifier
Foo foo;
@Test
public void testFoo() {
assertEquals(42, foo.getValue());
}
}
When using the WeldInitiator
as @ClassRule
to only have one weld instance for the whole test class,
inject(this)
cannot be used, as no this
reference is available yet and it cannot be done on the WeldInitiator
itself.
For this situation WeldInitiator
provides the method getTestClassInjectorRule()
that returns a MethodRule
which can be used as a @Rule
and injects into the test instance automatically:
class InjectTest {
@ClassRule
public static WeldInitiator weld = WeldInitiator.from(Foo.class).build();
@Rule
public MethodRule testClassInjectorRule = weld.getTestClassInjectorRule();
// Gets injected by testClassInjector when testFoo() is about to be run
@Inject
@MyQualifier
Foo foo;
@Test
public void testFoo() {
assertEquals(42, foo.getValue());
}
}
WeldInitiator.Builder.activate(Object)
makes it possible to activate and deactivate contexts for the specified normal scopes for each test method execution:
class ContextsActivatedTest {
@Rule
public WeldInitiator weld = WeldInitiator.from(Foo.class, Oof.class)
.activate(RequestScoped.class, SessionScoped.class).build();
@Test
public void testFoo() {
// Contexts for @RequestScoped and @SessionScoped are active!
// Foo is @RequestScoped
weld.select(Foo.class).get().doSomethingImportant();
// Oof is @SessionScoped
weld.select(Oof.class).get().doSomethingVeryImportant();
}
}
Sometimes you might need to add a mock for a bean that cannot be part of the test deployment, e.g. the original bean implementation has dependencies which cannot be satisfied in the test environment. Very often, it's an ideal use case for mocking libraries, ie. to create a bean instance with the desired behavior. In this case, there are two options. The first option is to add a producer method to the test class and add the test class to the deployment. The test class will be recognized as a bean and therefore the producer will also be discovered.
interface Bar {
String ping();
}
class Foo {
@Inject
Bar bar;
String ping() {
return bar.ping();
}
}
class TestClassProducerTest {
@Rule
public WeldInitiator weld = WeldInitiator.from(Foo.class, TestClassProducerTest.class).build();
@ApplicationScoped
@Produces
Bar produceBar() {
// Mock object provided by Mockito
return Mockito.when(Mockito.mock(Bar.class).ping()).thenReturn("pong").getMock();
}
@Test
public void testFoo() {
Assert.assertEquals("pong", weld.select(Foo.class).get().ping());
}
}
This should work in most of the cases (assuming the test class meets some conditions) although it's a little bit cumbersome.
The second option is WeldInitiator.Builder.addBeans(Bean<?>...)
which makes it possible to add beans during AfterBeanDiscovery
phase easily.
You can provide your own jakarta.enterprise.inject.spi.Bean
implementation or make use of existing solutions such as DeltaSpike BeanBuilder or for most use cases a convenient org.jboss.weld.junit.MockBean
should be sufficient.
Use org.jboss.weld.junit.MockBean.builder()
to obtain a new builder instance.
interface Bar {
String ping();
}
class Foo {
@Inject
Bar bar;
String ping() {
return bar.ping();
}
}
class AddBeanTest {
@Rule
public WeldInitiator weld = WeldInitiator.from(Foo.class).addBeans(createBarBean()).build();
static Bean<?> createBarBean() {
return MockBean.builder()
.types(Bar.class)
.scope(ApplicationScoped.class)
.creating(
// Mock object provided by Mockito
Mockito.when(Mockito.mock(Bar.class).ping()).thenReturn("pong").getMock())
.build();
}
@Test
public void testFoo() {
assertEquals("pong", weld.select(Foo.class).get().ping());
}
}
Sometimes it might be useful to add a mock interceptor, e.g. if an interceptor implementation requires some environment-specific features.
For this use case the org.jboss.weld.junit.MockInterceptor
is a perfect match:
@FooBinding
class Foo {
boolean ping() {
return true;
}
}
@Target({ TYPE, METHOD })
@Retention(RUNTIME)
@InterceptorBinding
@interface FooBinding {
@SuppressWarnings("serial")
static final class Literal extends AnnotationLiteral<FooBinding> implements FooBinding {
public static final Literal INSTANCE = new Literal();
};
}
}
class MockInterceptorTest {
@Rule
public WeldInitiator weld = WeldInitiator.from(Foo.class).addBeans(
MockInterceptor.withBindings(FooBinding.Literal.INSTANCE).aroundInvoke((ctx, b) -> {
return false;
})).build();
@Test
public void testInterception() {
Assert.assertFalse(weld.select(Foo.class).get().ping());
}
}
If a bean under the test declares a non-CDI injection point (such as @Resource
) a mock injection service must be installed.
WeldInitiator
builder comes with several convenient methods which allow to easily mock the Weld SPI:
bindResource()
- to handle@Resource
setEjbFactory()
- to handle@EJB
setPersistenceUnitFactory()
- to handle@PersistenceUnit
setPersistenceContextFactory()
- to handle@PersistenceContext
class Baz {
@Resource(lookup = "somejndiname")
String coolResource;
}
class MyTest {
@Rule
public WeldInitiator weld = WeldInitiator.from(Baz.class).bindResource("somejndiname", "coolString").build();
@Test
public void test(Baz baz) {
Assertions.assertEquals("coolString", baz.coolResource);
}
}