This guide introduces you to Arquillian’s Container SPI for the purpose of developing a new container adapter. After reading this guide, you’ll be able to:
- Write your own Arquillian container adapter
- Add support for select programming models in tests
- Test an Arquillian extension
- Use the container adapter to run tests in the new container
You’ll learn all of these skills by creating an Arquillian extension project that has a Maven build. We’ve designed this guide to be a fast read to get you started quickly!
Assumptions
This guide assumes you are familiar with using Arquillian. You should understand the basic building blocks of Arquillian, how to set it up and how to develop and execute tests. If you don’t, you’ll have better chance of success by first reviewing the two starter guides, Getting Started and Getting Started: Rinse and Repeat.
Obviously, you should be familiar with the container for which you are writing an adapter, specifically its deployment mechanism and/or API. It also helps to understand Arquillian’s event model.
Finally, this guide assumes you are familiar with Maven. All the reference container adapters provided by Arquillian are organized as Maven projects and we will be following that lead in this guide.
What is a Container Adapter?
A container, generally speaking, is a runtime with which Arquillian interacts when running a test. Arquillian may manage the container’s lifecycle or simply bind to it. Arquillian may deploy archives or descriptors to the container. Arquillian may transfer execution of the tests into the container, or it may simply make the URL of the deployed application available to the test which is acting as a client. What’s important is that the container interaction is transparent to the test.
To recollect a few salient points from the getting started guides, Arquillian supports containers through its extension mechanism. A container adapter is packaged as a JAR and is referenced, along with its dependencies, through as set of Maven coordinates (groupId, artifactId, version). During the execution of tests, a container adapter must be present in the test classpath, allowing Arquillian to locate, load and use it.
The notion of a container is not restricted to a Servlet container, an EJB container or a Java EE container, despite these being the most common types. A container could be abstracted away as a JVM process (like most Servlet containers and Java EE containers), a cloud service provider (OpenShift and CloudBees) or even a device (Android). Simply put, a container adapter enables Arquillian to communicate with a container so that the test doesn’t have to.
How a container adapter gets discovered
Container adapters are Arquillian extensions located using the Service Provider Interface (SPI) mechanism in Java. All Arquillian container adapter extensions must implement the LoadableExtension
SPI. The LoadableExtension
of a container is used to register a DeployableContainer
service. A container adapter implements the DeployableContainer
SPI in a manner specific to the container, to enable Arquillian to manage the lifecycle, make deployments and to execute tests in the container.
Setup the Project
One must first create a new project (preferably in Maven) to compile and package the container adapter.
Arquillian container adapters are usually created with the groupId of org.jboss.arquillian.container
, and with an artifactId of arquillian-{container_acronym}-{managed|remote|embedded}-{container_version}
.
Update the project POM with the following necessary dependencies.
<!-- clip --> <dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.arquillian</groupId> <artifactId>arquillian-bom</artifactId> <version>${version.arquillian_core}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<dependencies> <dependency> <groupId>org.jboss.arquillian.container</groupId> <artifactId>arquillian-container-spi</artifactId> </dependency> <dependency> <groupId>org.jboss.arquillian.container</groupId> <artifactId>arquillian-container-test-spi</artifactId> </dependency> </dependencies> <!-- clip -->
The version.arquillian_core
property in the above snippet should match the version of Arquillian Core that the adapter would be tested and built against.
Additional dependencies will be added in later sections.
Write the Extension
Implement the DeployableContainer
The DeployableContainer
implementation is the heart of a container adapter. Arquillian invokes the methods on the DeployableContainer
during various points in the test lifecycle. The table below lists the significance of some of these methods:
setup(T configuration) | Sets up the DeployableContainer instance. |
start() | Starts the container. Invoked before the test suite or class begins. Can be invoked manually via a ContainerController . |
stop() | Stops the container. Invoked after the test suite or class ends. Can be invoked manually via a ContainerController . |
deploy(org.jboss.shrinkwrap.api.Archive<?> archive) | Deploys an archive. Invoked before all test methods in a test class. |
undeploy(org.jboss.shrinkwrap.api.Archive<?> archive) | Undeploys an archive. Invoked after all the test methods in a test class. |
deploy(org.jboss.shrinkwrap.descriptor.api.Descriptor descriptor) | Deploys a descriptor (data source, JMS queue, etc). Executed before all the test methods in a test class. |
undeploy(org.jboss.shrinkwrap.descriptor.api.Descriptor descriptor) | Undeploys a descriptor. Executed after all the test methods in a test class. |
Implement the ContainerConfiguration
The ContainerConfiguration
type is a JavaBean-style class that is used to store properties required to assist in the operation of the container adapter. A container adapter creates a concrete implementation of this type. The properties are set by Arquillian from the arquillian.xml configuration file, arquillian.properties file or from commandline arguments. Note that Arquillian can set values for datatypes like Strings, Booleans, Integers etc. Complex properties are initialized to null even though their values may be provided.
You can validate the supplied property values in the validate method of the ContainerConfiguration
type.
Once you have defined a ContainerConfiguration
class for a container adapter, you must register it in the getConfigurationClass
method of the DeployableContainer
class of the adapter, like:
public class MyDeployableContainer implements DeployableContainer<MyContainerConfiguration> {
@Override
public Class<MyContainerConfiguration> getConfigurationClass() {
return MyContainerConfiguration.class;
}
}
Choose the protocol
An Arquillian protocol is the means employed by Arquillian to communicate with the container and to coordinate the execution of tests and collection of test results. Arquillian provides implementations out of the box for several protocols: JMX, Servlet 2.5 and Servlet 3.0. Note that the JMX protocol provided by Arquillian Core is an abstract protocol requiring a concrete implementation to be provided by the container adapter; one can refer the JBoss AS7 Arquillian container adapter for a real-world implementation. You can also implement custom protocols, however it’s beyond the scope of this guide.
The default protocol used by Arquillian to communicate with the container is determined primarily by the container adapter, though can be overriden in arquillian.xml or for a specific deployment through the @OverProtocol
annotation. Choosing the correct protocol is important since the choice is primarily driven by the capabilities of the container. Most Java EE 6 container adapters specify a default protocol of Servlet 3.0, whereas Java EE 5 container adapters specify a default protocol of Servlet 2.5.
The “Servlet 2.5” and “Servlet 3.0” strings are unique names identifying the protocol. Arquillian uses this information at runtime to enhance deployments with the test runners and communication endpoints required for the protocol.
The snippet below shows how to configure the Servlet 3.0 protocol for your container adapter.
// clip
@Override
public ProtocolDescription getDefaultProtocol() {
return new ProtocolDescription("Servlet 3.0");
}
// clip
You must also add the project containing the protocol implementation as a Maven dependency to your container adapter project. This will establish the protocol implementation as a transitive dependency for your container. The following snippet shows the addition of the arquillian-protocol-servlet
artifact to support the Servlet protocol.
<!-- clip -->
<dependencies>
<dependency>
<groupId>org.jboss.arquillian.protocol</groupId>
<artifactId>arquillian-protocol-servlet</artifactId>
</dependency>
</dependencies>
<!-- clip -->
Implement the lifecycle APIs for your adapter
Setup method
The setup(T configuration)
method is invoked by Arquillian after initializing the DeployableContainer
instance. An instance of the ContainerConfiguration
class associated with the container adapter is populated with user-supplied properties, validated, and provided as an argument to this method.
// clip private MyContainerConfiguration config;
@Override public void setup(MyContainerConfiguration configuration) { this.configuration = configuration; } // clip
Method implementations are often limited to storing the ContainerConfiguration
argument for future use by the container adapter in other parts of the adapter lifecycle, as shown in the above snippet.
Start method
The start()
method is invoked by Arquillian to signal the initialization of the container adapter. When this method returns, the container adapter is required to be ready to process deployments and test executions.
If you are implementing an embedded or a managed Arquillian container adapter, this is the place to start the container. In the case of remote container adapters, this is ideally the place to verify if the container is running, along with any other necessary validations.
In Arquillian, an embedded container runs in the same JVM as the client initiating the test. A managed container refers to one where Arquillian manages the entire lifecycle of the container. Managed container adapters usually fork a new process to operate the container, eventually terminating the process after all tests have been executed. A remote container refers to one where the lifecycle of the container is not managed by Arquillian. In this scenario, management of the remote container is often delegated to the build script and Arquillian is only responsible for deployment and execution of tests.
Stop method
The stop()
method is invoked by Arquillian to signal the termination of the container adapter. Any cleanup operations can be performed in this method.
It is possible for Arquillian to restart container adapters, especially in the case of managed containers. This is typically done to ensure that the managed JVM does not run out of space in it’s permanent generation. Therefore, one must not assume that the start and stop methods will be invoked exactly once in a test suite; they may be invoked multiple times with every start followed by an equivalent stop.
Implement the archive deployment APIs
Archive deploy method
This deploy(Archive<?> archive)
method is responsible for deploying the supplied archive to the target container. This is done in a manner proprietary to the container. Some containers may require a multi-part HTTP POST request to be submitted, some may require files to be placed in a certain directory and some require proprietary APIs to be invoked. This is where familiarity with your container is critical.
This deploy(Archive<?> archive)
method returns an instance of ProtocolMetaData
upon completion. The container adapter must introspect the container to obtain the necessary metadata used to construct the ProtocolMetaData
instance. In the case of the Servlet protocol, the ProtocolMetaData
contains the context root of the deployment, as well as the list of all Servlets under the context root. When this method returns, it is expected that the deployment can be reached via the information in the ProtocolMetaData
object.
Archive undeploy method
The undeploy(Archive<?> archive)
method is responsible for undeploying the previously deployed archive from the target container. Like the deploy(Archive<?> archive)
method, this is done in a manner proprietary to the container.
Optionally, implement the descriptor deployment APIs
Descriptor deploy method
This deploy(Descriptor descriptor)
method allows for deployment of descriptors. Simply put, deployment of descriptors allows for creation of datasources, JMS queues and other ressources on the container that will be used only for the duration of the test. Note that the Descriptor
type hierarchy is not formalized. Every container adapter is free to implement its own Descriptor
classes.
Descriptor undeploy method
The undeploy(Descriptor descriptor)
method is the converse of the deploy(Descriptor descriptor)
method for descriptors. It allows for undeployment of resources after test execution.
Add the test enrichers
Arquillian supports dependency injection for both in-container and client tests through the use of Arquillian test enrichers. Which injections are supported depend on the type of test and the capabilities of the container. Injection points (e.g., fields in the test class or arguments to test methods) can be annotated with @Resource
, @EJB
, @Inject
, @PersistenceContext
annotations, instructing Arquillian to inject these dependencies at runtime.
In order to support these annotations, the deployed archive must be enhanced with the Arquillian test enrichers. Additionally, a container may support only certain test enrichers natively. For instance, Java EE 5 containers may not support injection of @Inject
annotated dependencies out of the box, while a Servlet container adapter (like Tomcat) would not support injection of @EJB
annotated dependencies.
A container adapter declares the default set of test enrichers as classpath dependencies; at runtime, the test enrichers are made available as transitive dependencies of the container adapter. The test enricher projects contain the necessary auxiliary archive appenders that would enhance a deployment with the required set of classes and resources. Depending on the supported test enrichers, add one or all of the dependencies listed below, to your container adapter project.
<!-- clip --> <dependencies>
<!-- For @Inject annotations --> <dependency> <groupId>org.jboss.arquillian.testenricher</groupId> <artifactId>arquillian-testenricher-cdi</artifactId> </dependency>
<!-- For @EJB annotations --> <dependency> <groupId>org.jboss.arquillian.testenricher</groupId> <artifactId>arquillian-testenricher-ejb</artifactId> </dependency>
<!-- For @Resource annotations --> <dependency> <groupId>org.jboss.arquillian.testenricher</groupId> <artifactId>arquillian-testenricher-resource</artifactId> </dependency>
<!-- For @ArquillianResource annotated InitialContext injection points --> <dependency> <groupId>org.jboss.arquillian.testenricher</groupId> <artifactId>arquillian-testenricher-initialcontext</artifactId> </dependency>
</dependencies> <!-- clip -->
By including the dependencies as shown in the above snippet, the following extensions would be discovered by Arquillian at the time of test execution:
CDIEnricherExtension
EJBEnricherExtension
ResourceEnricherExtension
InitialContextExtension
The associated AuxiliaryArchiveAppender
implementations in these extensions would enhance the deployment with the test enrichers and associated dependencies. The TestEnricher
implementations in these extensions would also be registered as service providers that can be discovered in-container. When a test class instance is to be enriched (before execution of the contained tests), the registered test enrichers (that have been deployed alongside the test) will be used to discover and inject dependencies into the instance.
Register the Extension
To wire up the container adapter together, create a new Arquillian extension by implementing the LoadableExtension
interface in your project. The extension must register the concrete implementation of DeployableContainer
as a service. In the following snippet, a LoadableExtension
is created that registers the MyDeployableContainer
type as a DeployableContainer
.
public class MyContainerExtension implements LoadableExtension {
@Override
public void register(ExtensionBuilder builder) {
builder.service(DeployableContainer.class, MyDeployableContainer.class);
}
}
Finally, the container adapter extension needs to be registered using the SPI registration mechanism. You must create a file named org.jboss.arquillian.core.spi.LoadableExtension as shown below and put the fully qualified class name of the class implementing the LoadableExtension
SPI on the first line.
org.jboss.arquillian.container.guide.spi.MyContainerExtension
Write the Tests
While one can write a container adapter without tests, this is certainly not advisable. Tests can not only serve to define the expected behavior of the adapter, but also serve as useful examples for using the adapter. Generally, tests are written to cover the following areas:
- Deployment of various types of ShrinkWrap archives: JavaArchive, WebArchive and EnterpriseArchive.
- Execution of tests run at the client, using the
@RunAsClient
annotation and injection of dependencies into such tests using the@ArquillianResource
annotation. - Execution of tests run in-container and injection of dependencies into such tests using the supported annotations.
Share the Knowledge
Find this guide useful?
There’s a lot more about Arquillian to cover. If you’re ready to learn more, check out the other available guides.
Feedback
Find a bug in the guide? Something missing? You can fix it by forking this website, making the correction and sending a pull request. If you’re just plain stuck, feel free to ask a question in the user discussion forum.
Recent Changelog
- Sep 21, 2017: Fix(scripts) timeout for waiting for timestamp available on pages is by Matous Jobanek