CdiMock is a JUnit 5 extension to support mocking in CDI tests.
Add the following maven dependency to your pom.xml
<dependency>
<groupId>rocks.limburg.cdimock</groupId>
<artifactId>cdimock</artifactId>
<version>2.0.0</version>
</dependency>
In a JUnit 5 test thats starts a CDI container (see here how to do that),
simply add @EnableCdiMocking
or @ExtendWith(CdiMocking.class)
at class level,
declare fields of the types you want to mock and annotate them with @CdiMock
.
Every bean of that type in your deployment then will be replaced by the content of that field.
@ExtendWith(CdiMocking.class)
class CdiTest {
@CdiMock
Configuration mockConfiguration = new TestConfiguration();
@Inject
HelloService helloService;
... // setup CDI container here
@Test
void hello() {
assertEquals("hello TestConfiguration", helloService.hello());
}
}
If you want to use Mockito for mocking, you can combine the MockitoExtension for JUnit 5 with the CdiMocking extension.
It is necessary that the MockitoExtension is registered before the CdiMocking extension.
This can be done by declaring both extensions via @ExtendWith
in the appropriate order.
You can then use Mockito's @Mock
annotation. Mockito then will inject the mocks and CdiMock will pick them up as CDI beans.
@ExtendWith({MockitoExtension.class, CdiMocking.class})
class CdiTest {
@Mock
Configuration mockConfiguration;
...
}
You can also place @MockitoSettings
above @EnableCdiMocking
as current compilers and jvms preserve the annotation order.
@MockitoSettings
@EnableCdiMocking
class CdiTest {
@Mock
Configuration mockConfiguration;
}
Note that this behavior is not specified by the jvm spec so it may change in upcoming releases without notice.
If you want to configure mocktio via MockitoSettings
and be sure to initialize the extensions in the correct order,
you can define the mockito extension twice. JUnit will ignore the second declaration and ensure that the extensions are in the appropriate order.
The configuration of mockito will take place nonetheless.
@ExtendWith({ MockitoExtension.class, CdiMocking.class })
@MockitoSettings(strictness = LENIENT)
class CdiTest {
...
}
When using CdiMock together with a cdi testing framework that configures and starts the container for you,
like OpenWebBeans JUnit 5 extension or
the JUnit 5 extension of Meecrowave Testing
ensure, that the CdiMocking
extension is declared before the respective cdi extensions.
@ExtendWith({ MockitoExtension.class, CdiMocking.class, CdiExtension.class })
@MockitoSettings(strictness = LENIENT)
@Cdi
class CdiTest {
...
}
As Deltaspike Test-Control currently does not support JUnit 5, CdiMock will not work with Deltaspike Test-Control. However you may use Deltaspike CdiContainer and ContextControl to start your CDI container and enable certain scopes in your tests.
Since Arquillian currently does not support JUnit 5, CdiMock has no integration with Arquillian.
When you have a large test suite with many CDI tests, it is inappropriate to start and stop the cdi container
for every single test class. It will decrease build performance in an inacceptable amount.
See here how to implement starting one container for multiple tests.
In such scenario it is not eligible to define mock types per test class. Instead of that, you have to define
a set of mocks for the whole test execution (the same mock types for every test in that execution).
When you define mocks as described above, undefined behavior occurs (i.e. only the mocks of the first executed tests will be configured).
Instead of that you have to define CDI beans for your mocks and annotate them with @CdiMock
.
CdiMock provides CDI events of type ExtensionContext
for the JUnit lifecycle events
with the respective qualifiers @BeforeAll
, @BeforeEach
, @AfterEach
and @AfterAll
.
They can be used to reset the mocks for every test.
@ApplicationScoped
public class MockConfigurationProvider {
@Produces
@CdiMock
private Configuration mockConfiguration;
@PostConstruct
public void initMocks() {
mockConfiguration = Mockito.mock(Configuration.class);
}
public void resetMocks(@Observes @BeforeEach Object event) {
Mockito.reset(mockConfiguration);
}
}
Enable mocking by placing a beans.xml
in the META-INF
folder of your test folder that enables the alternative stereotype
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
bean-discovery-mode="all"
version="2.0">
<alternatives>
<stereotype>rocks.limburg.cdimock.CdiMock</stereotype>
</alternatives>
</beans>
With such configuration mocks can be injected into the test instance like any other CDI bean and then can be configured (i.e. using Mockito) as desired.
In the previous section MockConfigurationProvider
had to be implemented manually. However, when Mockito is in the classpath
you can inject such mocks automatically by declaring their types via the @MockitoBeans
annotation.
@MockitoBeans(types = Configuration.class)
class CdiTest {
...
}
As described, there is one set of mock types per container start. With JUnit 5 Tags you can define different groups of tests and with maven, the surefire plugin can be configured to execute these groups in different executions.
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>default-test</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<groups>set-of-mocks-1</groups>
<excludedGroups>set-of-mocks-2,set-of-mocks-3</excludedGroups>
</configuration>
</execution>
<execution>
<id>scenario2</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<groups>set-of-mocks-2</groups>
<excludedGroups>set-of-mocks-1,set-of-mocks-3</excludedGroups>
</configuration>
</execution>
<execution>
<id>scenario3</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<groups>set-of-mocks-3</groups>
<excludedGroups>set-of-mocks-1,set-of-mocks-2</excludedGroups>
</configuration>
</execution>
</executions>
</plugin>
In a scenario with different sets of mocks you will need to exclude the mocks you don't need for that scenario.
Otherwise you would get an AmbiguousResolutionException
for that bean types.
CdiMock provides the @CdiExclude
annotation to support exclusion of classes from a certain deployment.
The classes can be specified by name (using the classes
attribute)
or by the annotation type of an annotation they are annotated with (using the classesAnnotatedWith
attribute).
Side note: With the @CdiExclude
annotation present, @EnableCdiMocking
is not required.
@Tag("set-of-mocks-1")
@CdiExclude(classes = { SetOfMocks2.class, SetOfMocks3.class })
class CdiTest {
...
}
You need to add this annotations to every test of scenario one so it is sensible to make use of JUnit 5 meta-annotations and use them at the tests.
@Tag("set-of-mocks-1")
@CdiExclude(classesAnnotatedWith = { Scenario2Test.class, Scenario3Test.class })
@Target({ANNOTATION_TYPE, TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Scenario1Test {
}
@Scenario1Test
class CdiTest {
...
}