Automatically integration test an ejb with Maven

Filed under: J2EE, Java, — Tags: CI, Cargo, Continuous integration, JUnit, TDD, Test automation — Thomas Sundberg — 2010-12-21

We want to perform an integration test of a system using Maven. The application must be deployed on an application server and the application server must be started before we can perform the integration tests. The application should be undeployed and the application server should be terminated after the integration test has been performed no matter what the result of the test was.

How can this be achieved with Maven?

We will start with setting up a Maven structure. We want one Maven module for the integration test so it can be clearly separated from any unit tests. We also want to build an enterprise archive that can be deployed, and we need some application logic. This sums up to four Maven modules organized as below:

root -- product -- business-api
                -- ear
                -- ejb
     -- integration-test
The entire project will be tied together with an aggregation pom

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>se.sigma.educational.maven.integration.test</groupId>
    <artifactId>integration-test-with-maven</artifactId>
    <version>1.0</version>
    <packaging>pom</packaging>
    <modules>
        <module>product</module>
        <module>integration-test</module>
    </modules>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <repositories>
        <repository>
            <id>JBOSS</id>
            <name>JBoss Repository</name>
            <url>http://repository.jboss.org/nexus/content/groups/public-jboss/</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

We will need JBoss specific things in the integration test and in the ejb module. I don't want to specify a JBoss repository more then once so I do it in a top pom, http://repository.jboss.org/nexus/content/groups/public-jboss.

JUnit will be used in all modules during the test phase so I define it here.

The product is tied together with another aggregation pom defined as

/product/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>se.sigma.educational.maven.integration.test</groupId>
        <artifactId>integration-test-with-maven</artifactId>
        <version>1.0</version>
    </parent>
    <artifactId>product</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>business-api</module>
        <module>ear</module>
        <module>ejb</module>
    </modules>
</project>

With all project preparations done, it's time to build the product.

The first thing I define is the business api. This will be the api that all the services will understand. I define it in a module of it's own since at least two other modules will depend on it. Both the ejb implementation and the integration test need to be able to implement and use the same interface.

Defining the business api in a separate module is the Maven way, rather than using the configuration property generateClient available in the ejb plugin.

In order to remove all complicated logic, I will create a simple Hello World. It will be sufficient and give me a chance to focus on the real problem.

The business api is defined as:

/product/business-api/src/main/java/se/sigma/educational/HelloWorld.java

package se.sigma.educational;

public interface HelloWorld {
    String sayHello(String name);
}

There is really nothing to it, it's a trivial java interface.

The Maven pom used to build it:

/product/business-api/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>se.sigma.educational.maven.integration.test</groupId>
        <artifactId>product</artifactId>
        <version>1.0</version>
    </parent>
    <artifactId>business-api</artifactId>
    <packaging>jar</packaging>
</project>

The service implementing the business api above can be tested as:

/product/ejb/src/test/java/se/sigma/educational/HelloWorldBeanTest.java

package se.sigma.educational;

import org.junit.Test;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

public class HelloWorldBeanTest {
    @Test
    public void sayHello() {
        HelloWorld helloWorld = new HelloWorldBean();
        String name = "Fridolf";
        String expected = "Hi " + name;
        String actual = helloWorld.sayHello(name);
        assertThat(actual, is(expected));
    }
}

The trivial implementation is:

/product/ejb/src/main/java/se/sigma/educational/HelloWorldBean.java

package se.sigma.educational;

import org.jboss.ejb3.annotation.RemoteBinding;

import javax.ejb.Remote;
import javax.ejb.Stateless;

@Stateless
@Remote(HelloWorld.class)
@RemoteBinding(jndiBinding = "HelloWorldJNDIName")
public class HelloWorldBean implements HelloWorld {
    public String sayHello(String name) {
        return "Hi " + name;
    }
}

The bean is defined as a @Stateless bean, it doesn't have any instance variables.

It is also defined as a @Remote(HelloWorld.class) which means that it implements the HelloWorld interface as a remote bean.

Finally it is defined to have a jndiBinding @RemoteBinding(jndiBinding = "HelloWorldJNDIName"), that is the name it will be available under in the application server.

The pom used to build the ejb looks like this:

/product/ejb/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>se.sigma.educational.maven.integration.test</groupId>
        <artifactId>product</artifactId>
        <version>1.0</version>
    </parent>
    <artifactId>ejb</artifactId>
    <packaging>ejb</packaging>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-ejb-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <ejbVersion>3.1</ejbVersion>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.ejb3</groupId>
            <artifactId>jboss-ejb3-ext-api</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>se.sigma.educational.maven.integration.test</groupId>
            <artifactId>business-api</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
</project>

The javaee-api is needed so we can get access to the ejb annotations during compile time. It will be provided during execution by the application server.

To be able to deploy the ejb as a part of a ear we need to package it. The packaging is done using a Maven module. The pom that performs the build looks like this:

/product/ear/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>se.sigma.educational.maven.integration.test</groupId>
        <artifactId>product</artifactId>
        <version>1.0</version>
    </parent>
    <artifactId>ear</artifactId>
    <packaging>ear</packaging>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-ear-plugin</artifactId>
                <version>2.4.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                    </archive>
                    <modules>
                        <jarModule>
                            <groupId>se.sigma.educational.maven.integration.test</groupId>
                            <artifactId>ejb</artifactId>
                            <includeInApplicationXml>true</includeInApplicationXml>
                        </jarModule>
                        <jarModule>
                            <groupId>se.sigma.educational.maven.integration.test</groupId>
                            <artifactId>business-api</artifactId>
                            <includeInApplicationXml>true</includeInApplicationXml>
                        </jarModule>
                    </modules>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>se.sigma.educational.maven.integration.test</groupId>
            <artifactId>ejb</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>se.sigma.educational.maven.integration.test</groupId>
            <artifactId>business-api</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
</project>

The product is done. Or rather, it ought to be done. We haven't tested it in an application server yet so we can't be sure. That's next.

So, we have a business interface, we have an implementation and we can package it as an ear. All we are missing is an integration test that deploys the ear, launches a JBoss, runs a set of tests and finally tears down the test bench.

The integration test looks like this:

/integration-test/src/test/java/se/sigma/educational/integration/test/HelloWorldIntegrationTest.java

package se.sigma.educational.integration.test;

import org.junit.Test;
import se.sigma.educational.HelloWorld;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Properties;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

public class HelloWorldIntegrationTest {
    @Test
    public void verifyHelloWorld() throws NamingException {
        Properties properties = new Properties();
        properties.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
        properties.put("java.naming.factory.url.pkgs", "=org.jboss.naming:org.jnp.interfaces");
        properties.put("java.naming.provider.url", "localhost:1099");

        Context ctx = new InitialContext(properties);
        HelloWorld reflector = (HelloWorld) ctx.lookup("HelloWorldJNDIName");

        String name = "Thomas";
        String expected = "Hi " + name;
        String actual = reflector.sayHello(name);
        assertThat(actual, is(expected));
    }
}

It's really nothing special here. A remote session bean is located, called and the answer is asserted.

The magic is in the Maven project that downloads a JBoss, deploys the ear, starts JBoss, runs the integration tests and finally tear up the test bench. This is done in a Maven project that looks like this:

/example/integration-test/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>se.sigma.educational.maven.integration.test</groupId>
        <artifactId>integration-test-with-maven</artifactId>
        <version>1.0</version>
    </parent>
    <artifactId>integration-test</artifactId>
    <packaging>jar</packaging>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <excludes>
                        <exclude>**</exclude>
                    </excludes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <includes>
                        <include>**/integration/**</include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <id>integration-test</id>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven2-plugin</artifactId>
                <version>1.0.5</version>
                <configuration>
                    <container>
                        <append>false</append>
                        <containerId>jboss51x</containerId>
                        <log>${project.build.directory}/logs/jboss51x.log</log>
                        <output>${project.build.directory}/logs/jboss51x.out</output>
                        <timeout>60000</timeout>
                        <zipUrlInstaller>
                            <installDir>${project.build.directory}/JBoss</installDir>
                            <url>http://downloads.sourceforge.net/project/jboss/JBoss/JBoss-5.1.0.GA/jboss-5.1.0.GA-jdk6.zip</url>
                        </zipUrlInstaller>
                    </container>
                    <configuration>
                        <type>standalone</type>
                        <home>${project.build.directory}/integration-test</home>
                        <properties>
                            <cargo.servlet.port>8080</cargo.servlet.port>
                            <cargo.jboss.configuration>default</cargo.jboss.configuration>
                            <cargo.rmi.port>1099</cargo.rmi.port>
                            <cargo.logging>high</cargo.logging>
                        </properties>
                        <deployables>
                            <deployable>
                                <groupId>se.sigma.educational.maven.integration.test</groupId>
                                <artifactId>ear</artifactId>
                                <type>ear</type>
                            </deployable>
                        </deployables>
                    </configuration>
                    <wait>false</wait>
                </configuration>
                <executions>
                    <execution>
                        <id>start-container</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>start</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>stop-container</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>stop</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.jboss.jbossas</groupId>
            <artifactId>jboss-as-client</artifactId>
            <version>5.1.0.GA</version>
            <type>pom.sha1.audit.json.sha1.audit.json</type>
        </dependency>
        <dependency>
            <groupId>se.sigma.educational.maven.integration.test</groupId>
            <artifactId>business-api</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>se.sigma.educational.maven.integration.test</groupId>
            <artifactId>ear</artifactId>
            <version>1.0</version>
            <type>ear</type>
        </dependency>
    </dependencies>
</project>

The integration test pom is where the magic happens.

There are a few things to notice in the integration-test pom.

First, we need access to an implementation of org.jnp.interfaces.NamingContextFactory when the test are executed. We will get the access to it if we add a dependency to jboss-as-client.

The next thing to notice is that I make sure that no test are executed by the surefire plugin. This is done by excluding everything in the default configuration.

Instead, we want to execute the tests using the failsafe plugin. Include everything in the package integration and execute the goals integration-test and verify in the Maven life cycle phase integration-test.

The most important configuration is the one done for the cargo plugin. First I start with defining that I want a JBoss 5.1 application server by defining the containerId to jboss51x

Note that you must set either the home element or define a zipUrlInstaller element. I have defined a zip url installer so a JBoss will be downloaded and installed. The reason for this is simple, I want to make sure that I perform the integration tests on a vanilla JBoss installation. Any configuration that I want to have done should be done in the built artifact. I want to be able to drop the ear on any JBoss and it should run without any configuration.

Then I define the container type to be a standalone which implies that I will always get a standalone installation of a JBoss application server. The type standalone also means that the installation always is deleted before a new execution. I define the home element to the Maven target directory. This means that the server is installed in ./target/.

Then I list the deployables that will be deployed. In this case there will only be one deployable.

Starting the application server is done in the pre-integration-test phase. Stopping the application server is done in the post-integration-test.

Run the integration test by executing

mvn clean install

This may take a while, there may be a lot of dependencies to download.

Done!

Resources



(less...)

Pages

About
Events
Why

Categories

Agile
Automation
BDD
Clean code
Continuous delivery
Continuous deployment
Continuous integration
Cucumber
Culture
Design
DevOps
Executable specification
Git
Gradle
Guice
J2EE
JUnit
Java
Javascript
Kubernetes
Linux
Load testing
Maven
Mockito
New developers
Pair programming
PicoContainer
Presentation
Programming
Public speaking
Quality
React
Recruiting
Requirements
Scala
Selenium
Software craftsmanship
Software development
Spring
TDD
Teaching
Technical debt
Test automation
Tools
Web
Windows
eXtreme Programming

Authors

Thomas Sundberg
Adrian Bolboaca

Archives

Meta

rss RSS