Test-Control Module

Overview

The Test-Control module enables you to write CDI-based tests easily. Calls to stop and start the CDI container are built into the Test-Control API, with simplified commands for customizing the management of contexts and other aspects during testing.

Project Setup

The configuration information provided here is for Maven-based projects and it assumes that you have already declared the DeltaSpike version and DeltaSpike Core module for your projects, as detailed in Configure DeltaSpike in Your Projects. For Maven-independent projects, see Configure DeltaSpike in Maven-independent Projects.

1. Declare Test-Control Module Dependencies

Add the Test-Control module to the list of dependencies in the project pom.xml file using this code snippet:

<dependency>
    <groupId>org.apache.deltaspike.modules</groupId>
    <artifactId>deltaspike-test-control-module-api</artifactId>
    <version>${deltaspike.version}</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.apache.deltaspike.modules</groupId>
    <artifactId>deltaspike-test-control-module-impl</artifactId>
    <version>${deltaspike.version}</version>
    <scope>test</scope>
</dependency>

Or if you’re using Gradle, add these dependencies to your build.gradle:

     testCompile 'org.apache.deltaspike.modules:deltaspike-test-control-module-impl'
     testRuntime 'org.apache.deltaspike.modules:deltaspike-test-control-module-api'

2. Declare CDI-implementation-specific dependencies

The Test-Control module depends on the Container-Control module, which provides adapters for several major CDI implementations. Therefore, to use Test-Control, declare dependency on a CDI implementation and a corresponding Container Control implementation in the pom.xml.

OpenWebBeans

If you are using OpenWebBeans, add an OpenWebBeans implementation and the OpenWebBeans-specific Container Control module to the list of dependencies:

 <dependency>
     <groupId>org.apache.deltaspike.cdictrl</groupId>
     <artifactId>deltaspike-cdictrl-owb</artifactId>
     <version>${deltaspike.version}</version>
     <scope>test</scope>
 </dependency>

<dependency>
    <groupId>org.apache.openwebbeans</groupId>
    <artifactId>openwebbeans-impl</artifactId>
    <version>${owb.version}</version>
    <scope>test</scope>
</dependency>

Weld

If you are using Weld, add a Weld implementation and the Weld-specific Container Control module to the list of dependencies:

<dependency>
    <groupId>org.apache.deltaspike.cdictrl</groupId>
    <artifactId>deltaspike-cdictrl-weld</artifactId>
    <version>${deltaspike.version}</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.jboss.weld.se</groupId>
    <artifactId>weld-se-core</artifactId>
    <version>${weld.version}</version>
    <scope>test</scope>
</dependency>

OpenEJB

If you are using OpenWebBeans as the CDI implementation and you need to test EJBs as well, add the OpenEJB-specific Container Control module to the list of dependencies instead of the OpenWebBeans-specific Container Control module:

<dependency>
    <groupId>org.apache.deltaspike.cdictrl</groupId>
    <artifactId>deltaspike-cdictrl-openejb</artifactId>
    <version>${deltaspike.version}</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.apache.openejb</groupId>
    <artifactId>openejb-core</artifactId>
    <version>${openejb.version}</version>
    <scope>test</scope>
</dependency>

3. Complete Additional Project Configuration

Add a beans.xml file in the project test module (e.g. src/test/resources/META-INF/beans.xml).

Automated Container Booting and Shutdown

CdiTestRunner

Start and stop the CDI container automatically per test class with CdiTestRunner, a JUnit Test-Runner. This also starts and stops one request and session per test-method.

Example of CdiTestRunner Usage
@RunWith(CdiTestRunner.class)
public class ContainerAndInjectionControl
{
    @Inject
    private ApplicationScopedBean applicationScopedBean;

    @Inject
    private SessionScopedBean sessionScopedBean;

    @Inject
    private RequestScopedBean requestScopedBean;

    //test the injected beans
}

CdiTestSuiteRunner

Extend automated CDI container start and stop actions to whole test suites with CdiTestSuiteRunner, a JUnit Test-Suite-Runner.

Example of CdiTestSuiteRunner Usage
@RunWith(CdiTestSuiteRunner.class)
@Suite.SuiteClasses({
    TestX.class,
    TestY.class
})
public class SuiteLevelContainerControl
{
}

Optional Shutdown Configuration

You can set deltaspike.testcontrol.stop_container to false (via the standard DeltaSpike config), resulting in the CDI Container being started just once for all tests.

Test Customization

@TestControl

Customize the default behavior of CdiTestRunner with @TestControl. In the following case only one session for all test-methods (of the test-class) will be created.

Example of @TestControl Usage
@RunWith(CdiTestRunner.class)
@TestControl(startScopes = SessionScoped.class)
public class CustomizedScopeHandling
{
    //inject beans and test them
}

ProjectStage Control

Override the default ProjectStage for unit tests with ProjectStage.UnitTest.class.

Example of projectStage Usage
@RunWith(CdiTestRunner.class)
@TestControl(projectStage = CustomTestStage.class)
public class TestStageControl
{
    //tests here will see ProjectStage CustomTestStage.class

    @Test
    @TestControl(projectStage = ProjectStage.Development.class)
    public void checkDevEnv()
    {
    }

    //tests here will see ProjectStage CustomTestStage.class
}

Optional Configuration

From DeltaSpike 1.2, it is possible to provide a configuration for the underlying test-container. However, currently only the adapter for OpenEJB embedded (available in CDI-Control) supports it out-of-the-box. To pass properties to the underlying test-container, you have to add /META-INF/apache-deltaspike_test-container.properties to the resources-directory of your test-classpath. The content of the file are key/value pairs which get passed to the container. Therefore, it is a configuration which is not used by DeltaSpike itself (it is just forwarded (as it is) to the underlying test-container).

Reconfigure the config-file Name or Location

If you would like to point to an existing config-file, you have to add for example:

deltaspike.testcontrol.test-container.config-file=META-INF/existingConfig.properties

to /META-INF/apache-deltaspike.properties.

If you would like to do it per ProjectStage, you can use for example:

deltaspike.testcontrol.test-container.config-file.UnitTest=META-INF/unit-test/existingConfig.properties

Optional Integrations

Mock Frameworks

From DeltaSpike 1.0, it is possible to mock CDI-Beans. Usually @Exclude (+ ProjectStage) is enough, however, for some cases mocked beans might be easier. Therefore it is possible to create (mock-)instances manually or via a mocking framework and add them, for example, via DynamicMockManager.

Attention: Mocking CDI beans is not supported for every feature of CDI and/or every implementation version. For example, we can not mock intercepted CDI beans and with some implementations mocking specialized beans fails. Usually all features are active by default, however, due to those reasons we deactivated this feature by default. You can enable it by adding

deltaspike.testcontrol.mock-support.allow_mocked_beans=true and/or deltaspike.testcontrol.mock-support.allow_mocked_producers=true

to /META-INF/apache-deltaspike.properties in your test-folder.

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.

Example 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 is required to handle mocks for application-scoped beans differently, for example:

@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 is also possible to mock qualified beans. Just add the literal-instance(s) as additional parameter(s), for example:

@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 is necessary to use @javax.enterprise.inject.Typed. Mocking such typed beans can result in an AmbiguousResolutionException. Therefore it is necessary to exclude the mocked implementation via @Exclude or @Typed() (or a parametrized constructor) and specify the target-type via @TypedMock.

JSF (via MyFaces-Test)

add one of

  • org.apache.deltaspike.testcontrol.impl.jsf.MockedJsf2TestContainer

  • org.apache.deltaspike.testcontrol.impl.jsf.MockedJsfTestContainerAdapter

  • org.apache.deltaspike.testcontrol.impl.jsf.MyFacesContainerAdapter

  • org.apache.deltaspike.testcontrol.impl.jsf.MyFacesContainerPerTestMethodAdapter

as content to

/META-INF/services/org.apache.deltaspike.testcontrol.spi.ExternalContainer

(in your config-folder for tests, e.g. test/resources)

Using jersey-test with test-control

Jersey-test starts jetty which answers requests in a separated thread. Since ds test-control just handles the thread of the test itself, it’s needed to integrate jetty and jersey with the cdi-container. Usually that’s done via a ServletRequestListener - the following part describes an alternative approach for jersey-test:

//use: -Djersey.config.test.container.factory=custom.CdiAwareJettyTestContainerFactory

@RunWith(CdiTestRunner.class)
public class SimpleCdiAndJaxRsTest extends JerseyTest
{
  //...
}

or

public class CdiAwareJerseyTest extends JerseyTest
{
    static
    {
        System.setProperty("jersey.config.test.container.factory", CdiAwareJettyTestContainerFactory.class.getName());
    }
}

@RunWith(CdiTestRunner.class)
public class SimpleCdiAndJaxRsTest extends CdiAwareJerseyTest
{
    //...
}
public class CdiAwareJettyTestContainerFactory implements TestContainerFactory
{
    @Override
    public TestContainer create(final URI baseUri, final DeploymentContext context) throws IllegalArgumentException
    {
        return new CdiAwareJettyTestContainer(baseUri, context);
    }
}

CdiAwareJettyTestContainer is a copy of JettyTestContainerFactory.JettyTestContainer but with

HandlerWrapper cdiHandlerWrapper = new CdiAwareHandlerWrapper();
cdiHandlerWrapper.setHandler(this.server.getHandler());
this.server.setHandler(cdiHandlerWrapper);

after the line with JettyHttpContainerFactory#createServer

//activate the request-context e.g. via:
public class CdiAwareHandlerWrapper extends HandlerWrapper
{
    @Override
    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
    {
        CdiContainer cdiContainer = CdiContainerLoader.getCdiContainer();

        try
        {
            cdiContainer.getContextControl().startContext(RequestScoped.class);
            super.handle(target, baseRequest, request, response);
        }
        finally
        {
            cdiContainer.getContextControl().stopContext(RequestScoped.class);
        }
    }
}

Mixed Tests

Usually you should have one kind of tests per test-module. However, if you need to add, for example, 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
{
    //...
}

Known Restrictions

Liquibase

Liquibase invokes #toString in a AfterDeploymentValidation observer. that is not 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, for example:

org.apache.deltaspike.core.spi.activation.ClassDeactivator=myPackage.LiquibaseAwareClassDeactivator

Further details are available at deactivatable.

Gradle

Gradle by default does not put resources and compiled sources in to the same directory. When running a test using Gradle, this means your classes will not be in bean archives as defined by the CDI spec. To work around this, you need to set your main and test directories for resources to point to where the compiled code lives. This is an example of how to do that:

sourceSets {
    main {
        output.resourcesDir = output.classesDir
    }
    test {
        output.resourcesDir = output.classesDir
    }
}

// ensure you're excluding duplicates

jar {
    duplicatesStrategy = 'exclude'
}

SPI

MockFilter

Please make sure that you are aware of Integration of Mock Frameworks before you continue with this section.

If you would like to exclude some parts of your application- and/or test-code so that they aren’t eligible for the mocking mechanism, you can provide an own implementation of org.apache.deltaspike.testcontrol.spi.mock.MockFilter and register it in /META-INF/services/org.apache.deltaspike.testcontrol.spi.mock.MockFilter.

That’s quite special and you need to know the CDI-SPI a bit. To get an idea about the required steps, you can have a look at the default implementation used by DeltaSpike-Test itself.

Such a filter is also needed in case you would like to customize DeltaSpike-Test. For example to provide an @Alternative implementation for DynamicMockManager, you need to implement org.apache.deltaspike.testcontrol.api.mock.DynamicMockManager, annotate it with @Alternative, ensure that you keep the type-information with @Typed, configure the alternative bean in /META-INF/beans.xml (in the test-classpath) and provide a custom MockFilter (as described above) which excludes the custom mock-manager. (Otherwise DeltaSpike-Test will try to mock the custom mock-manager.)

ExternalContainer

org.apache.deltaspike.testcontrol.spi.ExternalContainer allows to integrate containers which get started after the CDI container. Currently DeltaSpike provides:

  • MockedJsf2TestContainer (integration with MyFaces-Test)