This is a detailed followup to an Arquillian talk I held at JAX Germany, having had the honour of being asked to do the talk by Aslak Knutsen (the Arquillian project lead). Aslak provided some useful pointers and information to get me up to spec for the talk, so many thanks Aslak. The entire blog source code can be found on GitHub here, so you might want to fork that first before reading on.
What is Arquillian Cube?
This article assumes that you are already familiar with the Arquillian extensible testing platform; if not, then you should pop over to the Arquillian site and learn about the invasion. Arquillian Cube is an project led by Alex Soto that enables what I like to call ‘Production Near’ Arquillian based unit tests against Docker images, with as little overhead as possible. Do you find yourself having to repeatedly create complex mocked environments to simulate simple Java EE component and or integration tests? Then Cube might be the ticket. Alex is a star and is always willing to invest time to help. You can read some recent news about him here. Thanks Alex for this great project.
Docker Machine
You will need to set up the Docker Toolbox or Boot2Docker for your operating system before you continue, you can find it here. Docker Machine effectively replaces the old Boot2Docker (although it basically still is Boot2Docker under the hood). It has been redesigned to make the provisioning of Docker images more accessible and less error prone. The Docker Machine must be running before testing begins.
Disable TLS on Docker 1.3.x for Boot2Docker
If for some reason you cannot use Docker Machine and you are still using an older version of Docker, or do not require Boot2Docker (eg. Linux), then you can skip the actions in this section and go straight to ‘Getting Started’. Please still read on, as this may still be of use to you should you wish to disable TLS.
As of version 1.3.x Docker has switched to using TLS for communication with the Docker API (Secure sockets over HTTPS on port 2376). This was an abrupt (undocumented, yet critical) change that breaks many tools that have been written for previous Docker versions. This currently includes Cube.
The quickest resolution is to use an older version or disable/revert TLS for the Docker API until things have caught up (I will blog about it as soon as I have time to work on it). I chose to disable TLS in my own local environment, as I wanted to use the latest Docker. It was not easy to get information on how to do that , so to make life sweet again follow the summarized steps below:
From a console type the following to access the Docker daemon shell:
boot2docker ssh
Gain root permissions:
sudo su -
Use the VI text editor to edit/create the docker profile:
vi /var/lib/boot2docker/profile
Type the following into the editor:
DOCKER_TLS="no"
Press the ‘ESC’ key and type ZZ to save the profile and quit the vi editor
Restart the Docker daemon:
/etc/init.d/docker restart
Exit the shell
exit
Restart the Docker image:
boot2docker stop
boot2docker start
You will see something like the following. To enable access to the Docker daemon from any console you can/should add these settings to your local OS environment variables, or copy them for reuse. To connect the Docker client to the Docker daemon, please set:
export DOCKER_HOST=tcp://192.168.59.103:2375
export DOCKER_CERT_PATH=''
unset DOCKER_TLS_VERIFY
Run a quick test:
curl http://192.168.59.103:2375/_ping
This should respond simply with ‘OK’
Note: If you do not have ´curl´ on Windows then you can download it here. It is a very useful tool to have.
Getting Started – The Usual POM Suspects
Every time you want to test against Docker images it is essential to have Docker Machine or Boot2Docker already configured and running. This should always be your first step before testing begins.
Using the blog source code as a reference you need to add the Cube dependencies to your projects pom.xml in addition to the usual Arquillian dependencies (at the time of this writing, version 1.0.0.Alpha7):
<dependency>
<groupId>org.arquillian.cube</groupId>
<artifactId>arquillian-cube-docker</artifactId>
<version>1.0.0.Alpha7</version>
<scope>test</scope>
</dependency>
Note: Always check for the latest version, the project is moving quickly:
http://arquillian.org/modules/cube-extension/
The Arquillian Cube Extension
Add the Cube extension to your arquillian.xml configuration file:
<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://jboss.org/schema/arquillian"
xsi:schemaLocation="http://jboss.org/schema/arquillian
http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<!--
Defines the Cube extension configuration.
Full documentation can be found here: https://github.com/arquillian/arquillian-cube
NOTE: The 'dockerContainers' YAML formatting/indentation is critical!
-->
<extension qualifier="docker">
<property name="serverVersion">1.21</property>
<property name="machineName">default</property>
<property name="connectionMode">STARTORCONNECTANDLEAVE</property>
<!--@formatter:off-->
<property name="dockerContainers">
tomee:
image: andygeede/webprofile
await:
strategy: polling
type: ping
ports: [8080, 8089]
iterations: 100
env: [JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dopenejb.system.apps=true -Dopenejb.deployments.classpath=true -Dtomee.remote.support=true -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
portBindings: [8089/tcp,8088/tcp,8080->8080/tcp]
</property>
<!--@formatter:on-->
</extension>
<!--
The qualifier name directly relates to the extension dockerContainers
YAML property 'tomee:'.
-->
<container qualifier="tomee" default="true">
<configuration>
<property name="deployerProperties">
openejb.deployer.binaries.use=true
java.naming.security.principal=tomee
java.naming.security.credentials=unsecured
</property>
</configuration>
</container>
</arquillian>
There are many options available; simply too many to list in this article. I will address the most important options, but if you want to expand on them then please visit the Cube site for the full range.
This specifies the Docker API version. Cube is known to work with, and has been tested on, version 1.12
<property name="serverVersion">1.12</property>
This specifies the docker machine name. I am using ‘default’, which is (you guessed it) the default name. You can actually create different docker machine named instances, and so this name is the one you want to run against.
<property name="machineName">default</property>
There are various connectionMode options, but I like this one as it bootstraps the docker image and leaves it running for super fast continuation tests. If your tests change the state of the image then you may want to use the default option of STARTANDSTOP.
<property name="connectionMode">STARTORCONNECTANDLEAVE</property>
This is where we define options that relate directly to the docker image that should be run for the tests. It uses a YAML (Yet Another Mark-up Language) syntax, so formatting and indentations are absolutely critical – Make sure your IDE never reformats it. The specified image was built using the dockerfile located here. It basically fires up a TomEE instance and a PostgreSQL database server (I’ll be showing you how to run persistence tests in my next blog).
<property name="dockerContainers">
tomee:
image: andygeede/webprofile
...
Note: The ‘tomee:’ property relates to the container qualifier:
<container qualifier="tomee"
Arquillian ‘Production Near’ Unit Test on a Dockerized Apache TomEE
With the environment in place we can now move on to creating a unit test. It will look and feel very similar to a standard Arquillian test, which is, of course, the intended idea.
I am going to be testing the simple HelloWorldServlet.java servlet.
The test class ArquillianCubeTest.java needs to run against the Arquillian test framework. This is done by annotating the test class with:
@RunWith(Arquillian.class)
public class ArquillianCubeTest {
Next I define a very simple (as simple as it gets) web application archive which will be deployed on the remote server. Specifying @Deployment(testable = false)
ensures that the archive is isolated from the actual test class, and is a truly remote deployment which runs the test in client mode:
@Deployment(testable = false)
public static WebArchive create() {
return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class);
}
Note: Using @Deployment(testable = false)
can disable certain plugins, like Jacoco. So it is possible to set it to true, in which case you must ensure that test methods are annotated with @RunAsClient
.
So, for the actual test. Adding the @RunAsClient
annotation means that the body of the test will be run within the runtime of the client, rather than being deployed and run on the remote server. This is as close to a production environment as we can get, hence the term ‘Production Near’.
@Test
@RunAsClient
public void test() throws IOException {
Amazingly enough, that is it! You can see the working example by cloning the blog code and running it.
git clone https://github.com/AndyGee/JAX.git arquillian
cd arquillian/arquillian-cube
mvn clean install
With this project, as a bonus, you will receive several other Arquillian examples ranging from simple to complex.
Summary
I hope you have enjoyed reading and learning about Arquillian Cube. Arquillian Cube is not intended to be a replacement for standard Arquillian tests, rather it is to compliment the already powerful solution with real combined ‘Production Near’ functional, integration and unit tests.
The deployed Docker image can be built to represent a true mock of just about any production environment that can be easily shared with developers within any enterprise organization. The burden of creating complex mocked environments for simple component and integration tests can be shifted away from individual developers to a centralized and more maintainable environment.