| :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] |