There are two extension here, both of which follow the extension mechanism introduced in JUnit 5.
Therefore, in order to use this extension in your test, you have to annotate your test class with @ExtendWith(WeldJunit5Extension.class)
or @ExtendWith(WeldJunit5AutoExtension.class)
respectively.
In their default behaviour, the extensions will automatically start/stop Weld SE container and inject into all your @Inject
fields and method parameters in the given test instance.
Furthermore you can provide configuration and modify Weld bootstrapping process in various ways - extensions, scope activation, interception, ...
This JUnit extension supports both test lifecycles as described by JUnit 5 - per method and per class.
Requirements are JUnit 5 and Java 8.
- Maven Artifact
- Configuration Versus Automagic
- WeldJunit5Extension
- WeldJunit5AutoExtension
- Additional Configuration
<dependency>
<groupId>org.jboss.weld</groupId>
<artifactId>weld-junit5</artifactId>
<version>${version.weld-junit}</version>
</dependency>
There are two extensions you can choose from. While both are ultimately achieving the same thing, each opts for a different approach.
WeldJunit5Extension.class
is the original one where you declaratively configure the container, much like booting Weld SE itself.
There are some additional builder patterns on top of that allowing for easy addition of mocked beans and such.
The advantage of this approach is that you have complete control over what gets into Weld container and can easily change that.
On the other hand, it may be rather verbose and requires you to add a specifically annotated field to every test (described below).
WeldJunit5AutoExtension.class
is more of an annotation based approach where you don't need any special field in your test class.
In fact, you don't need anything except the JUnit @ExtendWith
and our extension will try its best to find out what classes should be added to Weld container as beans.
This of course makes some assumptions on your tests which may not always be met, hence there is bunch of annotations which allow you to configure the container.
Pros of this approach are quick setup for basic cases, less verbose code and that eerie feeling that things are happening automagically.
On the not so bright side, automatic config is not almighty and in some cases will falter forcing you to add some configuration via annotations.
Last but not least, overly complex test scenarios may mean loads of annotations and you may be better off with the former extension.
No matter what extension you choose, do not mix them together!
The simplest way to use this extension is to annotate your test class with @ExtendWith(WeldJunit5Extension.class)
.
If you are in for shorter annotations, you can also use @EnableWeld
.
With just these annotations, Weld container will be started before each test is run and stopped afterwards.
This default behaviour includes:
- Bootstrapping Weld SE container with
- Disabled discovery
- Added test class package as source of beans
- Disabled concurrent deployment
- Injecting into test instance, e.g. into all
@Inject
fields - Injecting into method parameters of your test methods
- In case the type of the parameter matches a known and resolvable bean
- By default, Weld is greedy and will try to resolve all parameters which are known as bean types in CDI container
- If you wish to change this behaviour, please refer to additional configuration section
- Shutting down the container after test is done
And this is how you achieve it:
import org.jboss.weld.junit5.WeldJunit5Extension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(WeldJunit5Extension.class)
class BasicUsageTest {
@Inject
MyBean myBean;
@Test
public void testFoo(MyOtherBean otherBean) {
// Weld SE container is bootstrapped here and the injection points are resolved
}
}
org.jboss.weld.junit5.WeldInitiator
is an entry point you will want to define if you wish to customize how we bootstrap Weld.
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 javax.enterprise.inject.Instance
and therefore might be used to perform programmatic lookup of bean instances.
WeldInitiator
should be a public field annotated with @org.jboss.weld.junit5.WeldSetup
.
From there you can use static methods:
import org.jboss.weld.junit5.WeldInitiator;
import org.jboss.weld.junit5.WeldJunit5Extension;
import org.jboss.weld.junit5.WeldSetup;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(WeldJunit5Extension.class)
class MyNewTest {
@WeldSetup
public WeldInitiator weld = WeldInitiator.of(Some.class);
@Test
public void testFoo() {...}
}
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.
@ExtendWith(WeldJunit5Extension.class)
class SimpleTest {
@WeldSetup
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.
@ExtendWith(WeldJunit5Extension.class)
class AnotherSimpleTest {
@WeldSetup
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.
Everytime WeldJunit5Extension
processes your test instance, it will automatically resolve all @Inject
fields as well as attempt to resolve any test method parameters, should they be injectable beans.
@ExtendWith(WeldJunit5Extension.class)
class InjectTest {
@WeldSetup
public WeldInitiator weld = WeldInitiator.from(Foo.class).build();
// Gets injected before executing test
@Inject
@MyQualifier
Foo foo;
@Test
public void testFoo(Foo fooAsParam) {
assertEquals(42, foo.getValue());
assertEquals(42, fooAsParam.getValue());
}
}
WeldInitiator.Builder.activate(Class<? extends Annotation>...)
makes it possible to activate and deactivate contexts for the specified normal scopes for each test method execution:
@ExtendWith(WeldJunit5Extension.class)
class ContextsActivatedTest {
@WeldSetup
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();
}
}
@ExtendWith(WeldJunit5Extension.class)
class TestClassProducerTest {
@WeldSetup
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() {
Assertions.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 javax.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();
}
}
@ExtendWith(WeldJunit5Extension.class)
class AddBeanTest {
@WeldSetup
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();
};
}
}
@EnableWeld
class MockInterceptorTest {
@WeldSetup
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;
}
@EnableWeld
class MyTest {
@WeldSetup
public WeldInitiator weld = WeldInitiator.from(Baz.class).bindResource("somejndiname", "coolString").build();
@Test
public void test(Baz baz) {
Assertions.assertEquals("coolString", baz.coolResource);
}
}
To use this approach, annotate your test class with ExtendWith(WeldJunit5AutoExtension.class)
or just @EnableAutoWeld
.
By default, the extension will:
- Inspect your test class and try to figure out what bean classes it needs based on injection points (field and parameter injection both work)
- This is done by finding classes and verifying if they have bean defining annotation so make sure they do
- Add those classes to Weld container
- Process additional annotations on test class
@AddPackages
,@AddExtensions
,@ActivateScopes
, ...
- Annotates test classes with
@Singleton
and prevents another instantiation by Weld and instead substitutes the test instances provided by JUnit - Bootstrap Weld container
- Inject into test instances, e.g. into all
@Inject
fields - Inject into method parameters of your test methods
- In case the type of the parameter matches a known and resolvable bean
- By default, Weld is greedy and will try to resolve all parameters which are known as bean types in CDI container
- If you wish to change this behaviour, please refer to additional configuration section
- Shutting down the container after test is done
Here is a simple example using the default plus one additional annotation (@AddPackages
):
import org.jboss.weld.junit5.auto.beans.Engine;
import org.jboss.weld.junit5.auto.beans.V8;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@EnableAutoWeld
@AddPackages(Engine.class) // turn all legitimate classes inside Engine's package into CDI beans
public class BasicAutomagicTest {
@Inject
private V8 v8Engine;
@Inject
private V6 v6Engine;
@Test
void test() {
assertNotNull(V8Engine);
assertNotNull(v6Engine);
}
}
The default behaviour is powerful enough to handle basic cases where you simply want to inject a bean make assertions on it. However, it will not be enough if you want to, say, test your CDI extensions, enable custom interceptors or make sure certain scopes are active. Or if you want to inject interfaces instead of implementations of beans. For those cases, and many others, there are special annotations you can use - we will go over them, one at a time. At the end there is an example showing several of them.
Normally, only @ApplicationScoped
and @Dependent
beans work without any additional settings.
@ActivateScopes
annotation allows you to list scopes which are to be actived for the duration of the test.
Note that the duration is dependent on your setting of JUnit test lifecycle - it can be either per method or per class.
Using this annotation you can specify a list of Java classes which will be registered as beans with Weld container. Note that standard rules for beans apply (proxiability for instance).
This can be handy if you wish to operate with interfaces rather than implementation classes as the class scanning performed by the extension cannot know for sure which class is the implementation of given interface.
Adds the decorator class into deployment and enables it.
Adds the interceptor class into deployment and enables it.
Registers one or more extensions within Weld container; this is programmatic replacement for placing the extension in META-INF
.
Adds all bean classes from listed packages to Weld container.
Packages are selected by providing any bean class in the package.
You can also specify if this should be done recursively using the recursive
parameter.
Enables given alternative stereotype.
Selects and alternative for the test bean archive.
Excludes a bean, or multiple beans, that include a bean defining annotation (e.g. scope) from automatic discovery. This can be helpful to allow replacing a bean class with a different implementation; typically a mock.
The type of bean to exclude is implied by the annotated field's type or annotated method's return type. If the type is a base class or interface all beans extending/implementing that type will be excluded.
NOTE: This annotation will only exclude beans defined by class annotations.
It will not exclude beans of the implied type that are defined by @Produces
producer methods/fields or synthetic beans.
Also, current implementation excludes beans based on type, disregarding any qualifiers that are specified.
import org.jboss.weld.junit5.auto.ExcludeBean;
import org.jboss.weld.junit5.auto.WeldJunit5AutoExtension;
import org.junit.jupiter.api.Test;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.mock;
@EnableAutoWeld
class TestSomeFoo {
@Inject
SomeFoo someFoo; // SomeFoo depends upon application scoped bean Foo
@Produces
@ExcludeBean // Excludes beans with type Foo from automatic discovery
Foo mockFoo = mock(Foo.class); // mockFoo is now produced in place of original Foo impl
@Test
void test(Foo myFoo) {
assertNotNull(myFoo);
assertEquals(myFoo.getBar(), "mock-foo");
}
}
Excludes a set of classes with bean defining annotations (e.g. scopes) from automatic discovery. This can be helpful to allow replacing bean classes with a different implementation; typically a mock.
This annotation works as an inverse of @AddBeanClasses
hence usually requires actual bean implementation classes as parameters.
NOTE: This annotation will only exclude beans defined by class annotations.
It will not exclude beans of the specified type that are defined by Produces
producer methods/fields or synthetic beans.
This section describes any additional configuration options this extension offers.
As mentioned above, Weld is greedy when it comes to parameter injection. It will claim the ability to resolve any parameter which is known as a bean type inside the running CDI container. This is mainly for usability, as it would be annoying to constantly type additional annotations to mark which parameter should be injected and which should be left alone.
However, we are aware that this might cause trouble if more extensions are competing for parameter resolution.
In such case, you can turn on explicit parameter resolution and Weld will only resolve parameters which have at least one javax.inject.Qualifier
annotation on them.
There are two ways to enable it; firstly, you can do it globally, through system property - org.jboss.weld.junit5.explicitParamInjection=true
This property is also available as a constant in our extension class, e.g. you can use org.jboss.weld.junit5.WeldJunit5Extension.GLOBAL_EXPLICIT_PARAM_INJECTION
.
Secondly, you can use @ExplicitParamInjection
on your method, or test class.
In case of test class this annotation will enforce the presence on qualifiers on all methods.
Let's have a look at it:
@EnableWeld
@ExplicitParamInjection // all methods will now require explicit parameters
class ExplicitParamInjectionTest {
@Test
public void testThatParamsAreNotResolvedByWeld(Foo foo) {
// Weld will not attempt to resolve Foo, hence this test will fail unless there is another extension resolving it
}
@Test
public void testThatParamsAreResolvedByWeld(@Default Foo foo, @MyQualifier Bar bar) {
// Weld will resolve both of the parameters
}
}
As you might know, if you want to inject a bean where you would normally not use any qualifier, you can do that using @Default
qualifier (as shown in the code above).
This is in accordance with CDI specification, feel free to read more about it.
Unlike Arquillian Weld embedded container, weld-junit has bean archive isolation 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.
If set, 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.
Note that this configuration only makes difference if you run with enabled discovery; it won't affect your deployment if you use synthetic bean archive.
@Produces
,@Disposes
, and@Observes
don't work in@Nested
test classes which fail to meet valid bean requirements due to the lack of a no-arg constructor and Weld ignores them silently. However,@Inject
and parameter injection also work with@Nested
classes.