Category Archives: maven

EE integration testing using Arquillian

Writing tests in a EE environment is a challenge. Compared to test support in spring framework there is some work.

Fortunately though, thanks to Arquillian, there is hope.

A lot of the complications are related with the difference in environment. In spring framework, you include all the libraries in your application, which means everything is readily available. In EE you need a container which provides facilities which are not directly included. Arquillian fixes this by allowing you to run your tests inside the container.

An Arquillian tests is almost the same as a non-Arquillian test, except that you also have to build the deployment package to run the test. While the general recommendation seems that you make such package as small as possible, I believe this can be a challenge as you do need all dependencies for these minimal classes needed for the test. In my mind it seems best to simply deploy the full package under development (jar or war) to run your tests.

The situation in Spring is similar. While it is easy to have a different context for each test, in practice it is better to reduce the number of context for running tests. Partly to make sure there are no dangling dependencies and partly to reduce the run time of your test suite.

In Arquillian the complexity is again slightly increased as you also need to include all project dependencies (as in “other” jars). You have to specify the project classes to include and the location of all dependencies.

In my application I use a base class for all integration tests. The dependency details are picked up from the maven pom (including sub-dependencies – unfourtunately just including all dependencies at once did not work). For the project classes themselves, I use a helper to simply include all classes of a package include the sub-packages which are found in the source. You also need to make sure all required resources are managed. Here again they are scanned from the source.

@RunWith(Arquillian.class)
public abstract class AbstractIT {
 
    private static final String SOURCES_DIR = "src/main/java";
    private static final String RESOURCES_DIR = "src/main/resources";
 
    @Deployment
    public static Archive<?> createDeployment() throws Exception {
        String pathPrefix = ""; // trick to make this work from both the main as
        // module root directory
        File currentDir = new File(".");
        if (!currentDir.getCanonicalPath().endsWith("back-impl")) {
            pathPrefix = "back-impl/";
        }
        PomEquippedResolveStage pom = Maven.resolver().loadPomFromFile(pathPrefix + "pom.xml");
        File[] commonsLang = pom.resolve("org.apache.commons:commons-lang3").withTransitivity().asFile();
        File[] appApi = pom.resolve("be.myapp:myapp-api").withTransitivity().asFile();
        File[] jTransfoCore = pom.resolve("org.jtransfo:jtransfo-core").withTransitivity().asFile();
        File[] jTransfoCdi = pom.resolve("org.jtransfo:jtransfo-cdi").withTransitivity().asFile();
        File[] queryDsl = pom.resolve("com.mysema.querydsl:querydsl-jpa").withTransitivity().asFile();
        File[] flyway = pom.resolve("com.googlecode.flyway:flyway-core").withTransitivity().asFile();
        File[] festAssert = pom.resolve("org.easytesting:fest-assert").withTransitivity().asFile();
        File[] httpclient = pom.resolve("org.apache.httpcomponents:httpclient").withTransitivity().asFile();
        File[] deltaspikeCore = pom.resolve("org.apache.deltaspike.core:deltaspike-core-impl").withTransitivity().asFile();
        File[] deltaspikeData = pom.resolve("org.apache.deltaspike.modules:deltaspike-data-module-impl").withTransitivity().asFile();
        WebArchive war = ShrinkWrap.create(WebArchive.class, "test.war").
                addAsLibraries(commonsLang).
                addAsLibraries(appApi).
                addAsLibraries(jTransfoCore).
                addAsLibraries(jTransfoCdi).
                addAsLibraries(queryDsl).
                addAsLibraries(flyway).
                addAsLibraries(festAssert).
                addAsLibraries(httpclient).
                addAsLibraries(deltaspikeCore).
                addAsLibraries(deltaspikeData).
                addAsResource("META-INF/persistence.xml").
                addAsResource("META-INF/beans.xml");
        addAllPackages(war, "be.fluxtock.back.impl", new File(pathPrefix + SOURCES_DIR + "/be/fluxtock/back/impl"));
        addAllResources(war, pathPrefix + RESOURCES_DIR);
        return war;
    }
 
    /**
     * Add all packages starting with given prefix from given path.
     *
     * @param war war archive to add packages to
     * @param prefix base package
     * @param dir directory for the base package
     */
    private static void addAllPackages(WebArchive war, String prefix, File dir) {
        war.addPackage(prefix);
        for (File file : dir.listFiles(File::isDirectory)) {
            addAllPackages(war, prefix + "." + file.getName(), file);
        }
    }
 
    /**
     * Add all resources from the given directory, recursively. Only adds
     * subdirectories when they start with a lower case letter
     *
     * @param war war archive to add packages to
     * @param directory directory with resources to add
     */
    private static void addAllResources(WebArchive war, String directory) {
        for (File file : new File(directory).listFiles(pathname -> pathname.isFile() || Character.isLowerCase(pathname.getName().charAt(0)))) {
            addAllResources(war, "", file);
        }
    }
 
    private static void addAllResources(WebArchive war, String prefix, File dir) {
        if (dir.isDirectory()) {
            prefix += dir.getName() + "/";
            for (File file : dir.listFiles()) {
                addAllResources(war, prefix, file);
            }
        } else {
            war.addAsResource(dir, prefix + dir.getName());
        }
    }
 
}

To allow the dependencies to be resolved from the pom, you need an extra dependency.

<dependency>
    <groupId>org.jboss.shrinkwrap.resolver</groupId>
    <artifactId>shrinkwrap-resolver-impl-maven</artifactId>
    <version>2.0.0</version>
    <scope>test</scope>
</dependency>

When using a database to run your tests, you also need to make sure your database is not polluted by your tests. Something similar to Spring’s AbstractTransactionalJUnit4SpringContextTests can be built quite easily.

/**
 * Transactional integration test. Transaction is rolled back at the end.
 */
public abstract class AbstractRollbackIT extends AbstractIT {
 
    @PersistenceContext
    EntityManager em;
 
    @Inject
    UserTransaction utx;
 
    @Before
    public void setUp() throws Exception {
        utx.begin();
        em.joinTransaction();
    }
 
    @After
    public void tearDown() throws Exception {
        utx.rollback();
    }
 
}

With these base classes test are running fine. Unfortunately it does seem that a new deployment is created for each test. It would be nice (and faster) if tests which share the same deployment could also reuse that deployment – reducing the number of times the deployment is started in the container. Fortunately – compared to context initialization in spring framework – this initialization is very fast, but it would probably make a big difference when there are many integration tests.

Setting up WildFly for integration testing and Arquillian using maven

For a multi-module JEE7 project, I wanted to be able to have a WildFly setup which is ready for both integration testing using Arquillian and regular integration testing (REST client and Selenium).

To be able to test JEE code, Arquillian is a suitable option (the only I know of). This runs the tests in container. I did not manage to get the embedded container working with WildFly, so the Arquillian tests use the same WildFly version as the traditional integration tests.

From maven, using cargo is handy when dealing with application servers. This can install the server you want and it can run your deployed application, including waiting for the application to fully load.

The build was organized in a way to assure only one WildFly copy was built and configured and that copy is used for the tests in all modules and for for tests in the IDE.

Then configuration is entirely done in the parent pom for the project. Only modules which do client testing (using Selenium, REST tests or other) need the following configration (these need to be war or ear modules):

<plugin>
    <groupId>org.codehaus.cargo</groupId>
    <artifactId>cargo-maven2-plugin</artifactId>
</plugin>

This allows you to use “mvn cargo:run” on that module and also assures the container with the module deployed is running while doing integration tests.

We need quite a bit of configuration in the parent pom.
For starters, some properties:

<properties>
    <wildfly.home.parent>${project.basedir}/.cargo/extracts/wildfly-8.0.0.CR1/wildfly-8.0.0.CR1</wildfly.home.parent>
    <wildfly.home.module>${project.basedir}/../.cargo/extracts/wildfly-8.0.0.CR1/wildfly-8.0.0.CR1</wildfly.home.module>
    <jboss.home>${wildfly.home.module}</jboss.home>
 
    <postgresql.version>9.3-1100-jdbc41</postgresql.version>
    <wildfly.version>8.0.0.CR1</wildfly.version>
</properties>

We configure two locations where WildFly can be found, one to be used from child modules (one level down the directory tree), and one relative with the parent pom. We also configure an alias for the module path, partly because it us shorter, partly because the Arquillian WildFly container expects a jbossHome variable.

Some variables need to be known when the tests are running, so configure the following plugins:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.14.1</version>
    <configuration>
        <argLine>${argLine} -Xmx1124m -Xms256m</argLine>
        <systemPropertyVariables>
            <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
            <jboss.home>${jboss.home}</jboss.home>
            <module.path>${jboss.home}/modules</module.path>
        </systemPropertyVariables>
    </configuration>
</plugin>
 
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.16</version>
    <configuration>
        <argLine>${argLine} -Xmx1024m -Xms256m</argLine>
        <systemPropertyVariables>
            <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
            <jboss.home>${jboss.home}</jboss.home>
            <module.path>${jboss.home}/modules</module.path>
        </systemPropertyVariables>
    </configuration>
    <executions>
        <execution>
            <id>integration-test</id>
            <goals>
                <goal>integration-test</goal>
            </goals>
        </execution>
        <execution>
            <id>verify</id>
            <goals>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Now we need to configure cargo for starting and stopping the container on integration tests (in pluginManagement):

<plugin>
    <groupId>org.codehaus.cargo</groupId>
    <artifactId>cargo-maven2-plugin</artifactId>
    <version>1.4.6</version>
    <configuration>
        <container>
            <containerId>wildfly8x</containerId>
            <log>${basedir}/target/cargo.log</log>
            <output>${basedir}/target/wildfly.log</output>
            <zipUrlInstaller>
                <url>http://download.jboss.org/wildfly/8.0.0.CR1/wildfly-8.0.0.CR1.zip</url>
                <downloadDir>${project.basedir}/../.cargo/downloads</downloadDir>
                <extractDir>${project.basedir}/../.cargo/extracts</extractDir>
            </zipUrlInstaller>
        </container>
        <configuration>
            <properties>
                <cargo.servlet.port>8080</cargo.servlet.port>
                <cargo.logging>medium</cargo.logging>
                <cargo.jvmargs>${argLine} -Denv=test -Dtapestry.execution-mode=development</cargo.jvmargs>
                <cargo.jboss.configuration>standalone-full</cargo.jboss.configuration>
            </properties>
        </configuration>
    </configuration>
    <executions>
        <execution>
            <id>start-cargo</id>
            <phase>pre-integration-test</phase>
            <goals><goal>start</goal></goals>
            <configuration>
                <deployables>
                    <deployable>
                        <!-- Use exploded deploy: override location to point to the exploded webapp. -->
                        <location>${project.basedir}/target/${project.artifactId}-${project.version}</location>
                        <pingURL>http://localhost:8080/myApp</pingURL>
                        <pingTimeout>120000</pingTimeout> <!-- 2 min -->
                        <properties>
                            <context>myApp</context>
                        </properties>
                    </deployable>
                </deployables>
            </configuration>
        </execution>
        <execution>
            <id>stop-cargo</id>
            <phase>post-integration-test</phase>
            <goals><goal>stop</goal></goals>
        </execution>
    </executions>
</plugin>

This indicates that cargo needs to be started and stopped before and after integration tests. It also indicates where WildFly can be found.

To make this work, we need to assure the installed WildFly is preconfigured. In my case I need a fix to allow httpclient to be used and I need to assure the correct data source is installed (both the driver jar and the configuration itself). This can be done using the command:

mvn -Dsetup process-test-resources

The configuration is put in a profile to assure it is not included in every build. As the .cargo is not cleared when running “mvn clean” the configuration is long lasting.

<profile>
    <!-- setup wildfly container for use by cargo -->
    <id>setup-cargo</id>
    <activation>
        <property>
            <name>setup</name>
        </property>
    </activation>
    <build>
        <plugins>
            <plugin>
                <inherited>false</inherited>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven2-plugin</artifactId>
                <configuration>
                    <container>
                        <containerId>wildfly8x</containerId>
                        <zipUrlInstaller>
                            <url>http://download.jboss.org/wildfly/8.0.0.CR1/wildfly-8.0.0.CR1.zip</url>
                            <downloadDir>${project.basedir}/.cargo/downloads</downloadDir>
                            <extractDir>${project.basedir}/.cargo/extracts</extractDir>
                        </zipUrlInstaller>
                    </container>
                </configuration>
                <executions>
                    <execution>
                        <id>install-cargo</id>
                        <phase>process-test-resources</phase>
                        <goals><goal>install</goal></goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <inherited>false</inherited>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.8</version>
                <executions>
                    <execution>
                        <id>copy-db-driver</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>org.postgresql</groupId>
                                    <artifactId>postgresql</artifactId>
                                    <version>${postgresql.version}</version>
                                    <outputDirectory>${wildfly.home.parent}/standalone/deployments</outputDirectory>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <inherited>false</inherited>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <id>copy-resources-ds</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${wildfly.home.parent}/standalone/deployments</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>etc/wildfly/deploy/test</directory>
                                    <filtering>false</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                    <execution>
                        <id>copy-resources-wfly-2634</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${wildfly.home.parent}/modules/system/layers/base/org/jboss/resteasy/resteasy-jaxrs/main</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>etc/wildfly/deploy/WFLY-2634</directory>
                                    <filtering>false</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

To finish, your arquillian.xml needs to be configured correctly.

<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
 
    <defaultProtocol type="Servlet 3.0" />
 
    <container qualifier="jboss" default="true">
        <configuration>
            <property name="jbossHome">${jboss.home}</property>
            <property name="serverConfig">standalone-full.xml</property>
            <!-- uncomment below for debugging
            <property name="javaVmArguments">-Xmx512m -XX:MaxPermSize=128m -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8787</property>
            -->
        </configuration>
    </container>
</arquillian>

Using the maven build always uses the correct WildFly installation and my IDE (IntelliJ IDEA) allows me to run individual Arquillian tests.

Shrinkwrap add maven dependency for Arquillian and WildFly

When building deployable for Arquillian with Shrinkwrap, it is very useful to be able to add your maven dependencies. Fortunately this is possible using

PomEquippedResolveStage pom = Maven.resolver().loadPomFromFile(pathPrefix + "pom.xml");
File[] commonsLang = pom.resolve("org.apache.commons:commons-lang3").withTransitivity().asFile();
 
WebArchive war = ShrinkWrap.create(WebArchive.class, "test.war");
war.addAsLibraries(commonsLang);

This requires the following dependency

<dependencyManagement>
  <dependencies>
    <dependency>
        <groupId>org.jboss.arquillian</groupId>
        <artifactId>arquillian-bom</artifactId>
        <version>1.1.2.Final</version>
        <scope>import</scope>
        <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>
 
<dependencies>
    <dependency>
        <groupId>org.jboss.shrinkwrap.resolver</groupId>
        <artifactId>shrinkwrap-resolver-impl-maven</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Unfortunately, this does not work when you use the WildFly bom. You then get an error like

java.lang.RuntimeException: Could not invoke deployment method: public static org.jboss.shrinkwrap.api.Archive be.fluxtock.back.impl.AbstractIT.createDeployment() throws java.lang.Exception
	at org.jboss.arquillian.container.test.impl.client.deployment.AnnotationDeploymentScenarioGenerator.invoke(AnnotationDeploymentScenarioGenerator.java:177)
Caused by: java.lang.reflect.InvocationTargetException: null
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Caused by: java.lang.RuntimeException: Could not create object from user view
	at org.jboss.shrinkwrap.resolver.api.ResolverSystemFactory.createFromUserView(ResolverSystemFactory.java:95)
Caused by: java.lang.reflect.InvocationTargetException: null
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Caused by: java.lang.RuntimeException: Could not create new service instance
	at org.jboss.shrinkwrap.resolver.spi.loader.SpiServiceLoader.createInstance(SpiServiceLoader.java:240)
Caused by: java.lang.reflect.InvocationTargetException: null
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
Caused by: java.lang.NoClassDefFoundError: org/apache/maven/repository/internal/MavenRepositorySystemSession
	at org.jboss.shrinkwrap.resolver.impl.maven.bootstrap.MavenRepositorySystem.getSession(MavenRepositorySystem.java:84)
 
Caused by: java.lang.ClassNotFoundException: org.apache.maven.repository.internal.MavenRepositorySystemSession
	at java.net.URLClassLoader$1.run(URLClassLoader.java:359)

If you use the enforer plugin with the requireUpperBoundDeps test, you will see exceptions pointing in the right direction.

Failed while enforcing RequireUpperBoundDeps. The error(s) are [
Require upper bound dependencies error for org.apache.maven:maven-model:3.0.5 paths to dependency are:
+-be.fluxtock:fluxtock-back-impl:1.0-SNAPSHOT
  +-org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven:2.0.0
    +-org.apache.maven:maven-model:3.0.5
and
+-be.fluxtock:fluxtock-back-impl:1.0-SNAPSHOT
  +-org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven:2.0.0
    +-org.apache.maven:maven-aether-provider:3.1.1
      +-org.apache.maven:maven-model:3.1.1
and
+-be.fluxtock:fluxtock-back-impl:1.0-SNAPSHOT
  +-org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven:2.0.0
    +-org.apache.maven:maven-model-builder:3.0.5
      +-org.apache.maven:maven-model:3.0.5
, 
Require upper bound dependencies error for org.apache.maven:maven-model-builder:3.0.5 paths to dependency are:
+-be.fluxtock:fluxtock-back-impl:1.0-SNAPSHOT
  +-org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven:2.0.0
    +-org.apache.maven:maven-model-builder:3.0.5
and
+-be.fluxtock:fluxtock-back-impl:1.0-SNAPSHOT
  +-org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven:2.0.0
    +-org.apache.maven:maven-aether-provider:3.1.1
      +-org.apache.maven:maven-model-builder:3.1.1
, 
Require upper bound dependencies error for org.apache.maven:maven-repository-metadata:3.0.5 paths to dependency are:
+-be.fluxtock:fluxtock-back-impl:1.0-SNAPSHOT
  +-org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven:2.0.0
    +-org.apache.maven:maven-repository-metadata:3.0.5
and
+-be.fluxtock:fluxtock-back-impl:1.0-SNAPSHOT
  +-org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven:2.0.0
    +-org.apache.maven:maven-aether-provider:3.1.1
      +-org.apache.maven:maven-repository-metadata:3.1.1
]

It seems some settings in the WildFly and the Arquillian boms interfere, causing an invalid set of dependencies being included by maven.

To solve this, include the following section in your dependencyManagement section:

<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-aether-provider</artifactId>
    <version>3.0.5</version>
</dependency>
<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-model</artifactId>
    <version>3.0.5</version>
</dependency>
<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-model-builder</artifactId>
    <version>3.0.5</version>
</dependency>
<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-repository-metadata</artifactId>
    <version>3.0.5</version>
</dependency>