blob: 2ed48f081d5559578ca95b477d1e8de12097a314 [file] [log] [blame]
:moduledeps: core, container-control
:moduleconf: api:org.apache.deltaspike.testcontrol.api.junit.TestBaseConfig, impl:org.apache.deltaspike.testcontrol.impl.jsf.MyFacesTestBaseConfig
= Test-Control Module
:Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at. http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
== 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#, Configure DeltaSpike in Your Projects>>. For Maven-independent projects, see <<configure#config-maven-indep,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:
[source,xml]
----
<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`:
[source]
----
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:
[source,xml]
-----------------------------------------------------
<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:
[source,xml]
----------------------------------------------------
<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:
[source,xml]
----------------------------------------------------
<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
[source,java]
--------------------------------------------------------
@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
[source,java]
---------------------------------------
@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
[source,java]
-----------------------------------------------
@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
[source,java]
---------------------------------------------------------------
@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:
[source,Properties]
---------------------------------------------------------------
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:
[source,Properties]
---------------------------------------------------------------
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);`.
[source,java]
-------------------------------------------------------------
@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
[source,java]
----------------------------------------------------------------------------------
@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:
[source,java]
--------------------------------------------------------------------------------------------------------------------------
@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:
[source,java]
-------------------------------------------------------------
@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:
[source,java]
-------------------------------------------------------------------------------------------
//use: -Djersey.config.test.container.factory=custom.CdiAwareJettyTestContainerFactory
@RunWith(CdiTestRunner.class)
public class SimpleCdiAndJaxRsTest extends JerseyTest
{
//...
}
-------------------------------------------------------------------------------------------
or
[source,java]
-------------------------------------------------------------------------------------------
public class CdiAwareJerseyTest extends JerseyTest
{
static
{
System.setProperty("jersey.config.test.container.factory", CdiAwareJettyTestContainerFactory.class.getName());
}
}
@RunWith(CdiTestRunner.class)
public class SimpleCdiAndJaxRsTest extends CdiAwareJerseyTest
{
//...
}
-------------------------------------------------------------------------------------------
[source,java]
-------------------------------------------------------------------------------------------
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
[source,java]
-------------------------------------------------------------------------------------------
HandlerWrapper cdiHandlerWrapper = new CdiAwareHandlerWrapper();
cdiHandlerWrapper.setHandler(this.server.getHandler());
this.server.setHandler(cdiHandlerWrapper);
-------------------------------------------------------------------------------------------
after the line with JettyHttpContainerFactory#createServer
[source,java]
-------------------------------------------------------------------------------------------
//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:
[source,java]
---------------------------------------------
@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:
[source,java]
----------------------------------------------------------------------------------------------------------
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:
[source,groovy]
----------------------------------------------------------------------------------------------------------
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 <<__MockFrameworks, 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)
[TODO]