This module is available since version 0.6 and allows to write CDI based tests easily.
Setup for the CDI implementation of your choice and the following test-dependencies:
<dependency> <groupId>org.apache.deltaspike.modules</groupId> <artifactId>deltaspike-test-control-module-api</artifactId> <version>${ds.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.deltaspike.modules</groupId> <artifactId>deltaspike-test-control-module-impl</artifactId> <version>${ds.version}</version> <scope>test</scope> </dependency>
If you are using OpenWebBeans also add the following test-dependency
<dependency> <groupId>org.apache.deltaspike.cdictrl</groupId> <artifactId>deltaspike-cdictrl-owb</artifactId> <version>${ds.version}</version> <scope>test</scope> </dependency>
If you are using Weld also add the following test-dependency
<dependency> <groupId>org.apache.deltaspike.cdictrl</groupId> <artifactId>deltaspike-cdictrl-weld</artifactId> <version>${ds.version}</version> <scope>test</scope> </dependency>
JUnit Test-Runner to start/stop the CDI-Container autom. (per test-class) and one request and session per test-method:
@RunWith(CdiTestRunner.class) public class ContainerAndInjectionControl { @Inject private ApplicationScopedBean applicationScopedBean; @Inject private SessionScopedBean sessionScopedBean; @Inject private RequestScopedBean requestScopedBean; //test the injected beans }
@TestControl allows to change the default-behavior. In the following case only one session for all test-methods (of the test-class) will be created:
@RunWith(CdiTestRunner.class) @TestControl(startScopes = SessionScoped.class) public class CustomizedScopeHandling { //inject beans and test them }
JUnit Test-Suite-Runner to start/stop the CDI-Container autom. (per test-suite):
@RunWith(CdiTestSuiteRunner.class) @Suite.SuiteClasses({ TestX.class, TestY.class }) public class SuiteLevelContainerControl { }
It's possible to overrule the default-project-stage for unit-tests (ProjectStage.UnitTest.class):
@RunWith(CdiTestRunner.class) @TestControl(projectStage = CustomTestStage.class) public class TestStageControl { //tests here will see project-stage CustomTestStage.class @Test @TestControl(projectStage = ProjectStage.Development.class) public void checkDevEnv() { } //tests here will see project-stage CustomTestStage.class }
It's possible to set "deltaspike.testcontrol.stop_container" to "false" (via the std. DeltaSpike config). With that the CDI-Container will be started just once for all tests.
Don't forget to add a beans.xml in the test-module (e.g. src/test/resources/META-INF/beans.xml).
If you are using OpenWebBeans as CDI implementation and you need to test EJBs as well, you can use deltaspike-cdictrl-openejb + org.apache.openejb:openejb-core (instead of deltaspike-cdictrl-owb).
With v0.8+ it's possible to mock CDI-Beans. Usually @Exclude (+ project-stage) is enough, however, for some cases mocked beans might be easier. Therefore it's possible to create (mock-)instances manually or via a mocking framework and add them e.g. via DynamicMockManager
.
If you need dependency-injection in the mocked instances, you can use BeanProvider.injectFields(myMockedBean);
.
@RunWith(CdiTestRunner.class) public class MockedRequestScopedBeanTest { @Inject private RequestScopedBean requestScopedBean; @Inject private DynamicMockManager mockManager; @Test public void manualMock() { mockManager.addMock(new RequestScopedBean() { @Override public int getCount() { return 7; } }); Assert.assertEquals(7, requestScopedBean.getCount()); requestScopedBean.increaseCount(); Assert.assertEquals(7, requestScopedBean.getCount()); } } @RequestScoped public class RequestScopedBean { private int count = 0; public int getCount() { return count; } public void increaseCount() { this.count++; } }
Using a mocking framework makes no difference for adding the mock. E.g. via Mockito:
@RunWith(CdiTestRunner.class) public class MockitoMockedRequestScopedBeanTest { @Inject private RequestScopedBean requestScopedBean; @Inject private DynamicMockManager mockManager; @Test public void mockitoMockAsCdiBean() { RequestScopedBean mockedRequestScopedBean = mock(RequestScopedBean.class); when(mockedRequestScopedBean.getCount()).thenReturn(7); mockManager.addMock(mockedRequestScopedBean); Assert.assertEquals(7, requestScopedBean.getCount()); requestScopedBean.increaseCount(); Assert.assertEquals(7, requestScopedBean.getCount()); } }
Since CDI implementations like OpenWebBeans use a lot of optimizations, it's required to handle mocks for application-scoped beans differently - e.g.:
@RunWith(CdiTestRunner.class) public class MockedApplicationScopedBeanTest { @Inject private ApplicationScopedBean applicationScopedBean; @BeforeClass public static void init() { ApplicationMockManager applicationMockManager = BeanProvider.getContextualReference(ApplicationMockManager.class); applicationMockManager.addMock(new MockedApplicationScopedBean()); } @Test public void manualMock() { Assert.assertEquals(14, applicationScopedBean.getCount()); applicationScopedBean.increaseCount(); Assert.assertEquals(14, applicationScopedBean.getCount()); } } @ApplicationScoped public class ApplicationScopedBean { private int count = 0; public int getCount() { return count; } public void increaseCount() { this.count++; } } @Typed() //exclude it for the cdi type-check public class MockedApplicationScopedBean extends ApplicationScopedBean { @Override public int getCount() { return 14; } }
However, ApplicationMockManager
can be used for adding all mocks, if they should be active for the lifetime of the CDI-container.
It's also possible to mock qualified beans. Just add the literal-instance(s) as additional parameter(s) - e.g.:
@RunWith(CdiTestRunner.class) public class MockedQualifiedBeanTest { @Inject @MyQualifier private QualifiedBean qualifiedBean; @Inject private DynamicMockManager mockManager; @Test public void manualMockWithQualifier() { mockManager.addMock(new QualifiedBean() { @Override public int getCount() { return 21; } }, AnnotationInstanceProvider.of(MyQualifier.class)); Assert.assertEquals(21, qualifiedBean.getCount()); qualifiedBean.increaseCount(); Assert.assertEquals(21, qualifiedBean.getCount()); } }
In some cases it's needed to use @javax.enterprise.inject.Typed
. Mocking such typed beans can result in an AmbiguousResolutionException
.
Therefore it's needed to exclude the mocked implementation via @Exclude
or @Typed()
(or a parametrized constructor) and specify the target-type via @TypedMock
.
add on of
as content to
/META-INF/services/org.apache.deltaspike.testcontrol.spi.ExternalContainer
(in your config-folder for tests e.g.: test/resources)
Usually you should have one kind of tests per test-module. However, if you need to add e.g. a test without an external-container to your test-module which uses external-containers, you can annotate your test with:
@RunWith(CdiTestRunner.class) @TestControl(startExternalContainers = false) public class JsfContainerTest { //... }
Liquibase invokes #toString
in a AfterDeploymentValidation
observer.
that isn't portable and therefore you have to deactivate the mocking-support via:
public class LiquibaseAwareClassDeactivator implements ClassDeactivator { @Override public Boolean isActivated(Class<? extends Deactivatable> targetClass) { return !"org.apache.deltaspike.testcontrol.impl.mock.MockExtension".equals(targetClass.getName()); } }
and add LiquibaseAwareClassDeactivator
to /META-INF/apache-deltaspike.properties
- e.g.:
org.apache.deltaspike.core.spi.activation.ClassDeactivator=myPackage.LiquibaseAwareClassDeactivator
Further details are available at deactivatable.
org.apache.deltaspike.testcontrol.spi.ExternalContainer allows to integrate containers which get started after the CDI container. Currently DeltaSpike provides:
[TODO]