Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Testing Java EE Using Docker

DZone's Guide to

Testing Java EE Using Docker

Need to run an integration test for your JavaEE application? Well, here are some tools that can help you out and a roadmap of how to use them.

· Integration Zone
Free Resource

Today’s data climate is fast-paced and it’s not slowing down. Here’s why your current integration solution is not enough. Brought to you in partnership with Liaison Technologies.

In a previous post, I described how to test the persistence layer against a Dockerized database. This article carries this idea a step further by deploying the complete application to a Dockerized Wildfly server. The example application needs MariaDB, which is installed in the same Docker image as the Wildfly server.

The big benefit is that those integration tests feel like unit tests because they are completely self-contained. They can be run from the IDE as well as on the integration server without having a local application server or database server and without all those local machine-wise configurations. The only precondition is an available Docker host, either local or remote. Test classes can even run in parallel because each one gets it's own environment.

What Do We Need

  • A Docker host, local or remote.

  • A Docker image with the application server and database server installed.

  • Arquillian to deploy to the Dockerized server.

  • An Arquillian callback to start the Docker container and configure Arquillian to deploy to it.

  • testcontainers library to manage the Docker container.

Overview

A custom Docker image is used, which contains a Wildfly 10 and a MariaDB installation (kaiwinter/wildfly10-mariadb, available on Docker Hub). Wildfly is configured to use the MariaDB, and a management user is set-up to let Arquillian deploy to this server. The deployment is done by wildfly-arquillian-container-remote.

These are the rough steps when a test is started:

  1. Unit test is started.

  2. Arquillian runs our callback.

  3. Our callback starts the Docker container, configures Arquillian, and inserts database schema.

  4. Arquillian deploys the result of the @Deployment method.

  5. Arquillian runs every @Test method.

  6. Each @Testmethod inserts it's test data by DbUnit, calls test code and verifies the result.

The use of Docker is transparent for the test. You can use any existing Arquillian test without changes.

Setup

The tricky part is the dynamic configuration of Arquillian to let it deploy to the Dockerized Wildfly. This is necessary because the server ports are exposed on random ports to the outside of the Docker container to allow the parallel use of multiple instances of the same image. So after the Docker container is running we need to tell Arquillian the exposed management port of Wildfly.

Arquillian is configured by the file arquillian.xml and it cannot be changed by an API dynamically (GitHub Issue). But we can register a org.jboss.arquillian.core.spi.LoadableExtension service (via META-INF/services), which can register a listener on the configuration process.

The called listener starts the Docker container and blocks until it runs. Then it configures Arquillian and inserts the database schema (see WildflyMariaDBDockerExtension):

/**
 * Method which observes {@link ContainerRegistry}. Gets called by Arquillian at startup time.
 */
public void registerInstance(@Observes ContainerRegistry registry, ServiceLoader serviceLoader) {
    GenericContainer dockerContainer = new GenericContainer("kaiwinter/wildfly10-mariadb:1.1")
                                           .withExposedPorts(9990);
    dockerContainer.start();
    configureArquillianForRemoteWildfly(dockerContainer, registry);
    setupDb(dockerContainer);
}

Test example

The test looks like a common Arquillian test:

@RunWith(Arquillian.class)
public final class UserServiceTest {

    @Inject
    private UserService userService;

    @PersistenceContext
    private EntityManager entityManager;

    @Deployment
    public static EnterpriseArchive createDeployment() {
        // ... EAR creation
  }

    @Test
    public void testSumOfLogins() {
        DockerDatabaseTestUtil.insertDbUnitTestdata(entityManager,
                                                getClass().getResourceAsStream("/testdata.xml"));
        int sumOfLogins = userService.calculateSumOfLogins();
        assertEquals(9, sumOfLogins);
    }
}

This is the complete example: UserServiceTest.

Inserting testdata

There are several options to insert the data model and test data into the database:

  • .sql files (ScriptUtils class of testcontainers).

  • Single SQL statements.

  • DBUnit: Truncates all tables and inserts test data from XML files.

  • Flyway: When you need DB migrations.

The database model needs to be inserted only once. So the best place to do this is the Arquillian listener class where the Docker container is started. This can be done by a plain JDBC connection (example) or by a database migration library like Flyway (example).  

Then each unit test in the test class can insert its individual test data by DBUnit or from .sql files. The advantage of DBUnit is that it automatically removes existing data from the database. When you go with .sql files you may want to use transactions to separate the test cases.

Debugging

It is possible to debug the running application on the Dockerized Wildfly server. The Docker image exposes the port 8787 to let a debugger attach. 

Debugging With Eclipse

After setting a breakpoint in the server or test code (also the test code runs on the server), create a Debug Configuration for a Remote Java Application. Use the IP of the Docker Host for the connection. As mentioned earlier the container ports are exposed on dynamic ports. This means that after the container is started you have to check to which port the debugging port 8787 was mapped and have to put it in your Debug Configuration. This is a bit inconvenient.

Alternative to testcontainers

An alternative to the combination of Arquillian and testcontainers might be Arquillian Cube, which allows us to put a Docker Compose script in the arquillian.xml file. But when I was working on this (end of 2015) the project was a bit undocumented and I couldn't get it running. Today the documentation looks much better.

Summary

In the past, every developer got to have a local application server to deploy to and a local database server. This had to be configured on each developer's machine. The same had to be done for the integration server once. This setup time can be reduced to almost zero. When having a central Docker host a developer just have to set the DOCKER_HOST environment variable and can run integration tests. 

Benefits:

  • Reduced setup time for new developers.

  • Test running independent from local configuration.

  • Always an empty database without side effects.

  • Implicitly tested data model and migration scripts.

  • Multiple test classes can run concurrently, each runs in its own Docker container.

Links

Disclaimer

Docker and Arquillian both seem to be very polarizing. I try to keep a pragmatic sight on both topics, but I also try to avoid both. 

I wouldn't recommend to use what I described here as an "always-to-be-used" solution. You should always prefer small and fast unit test over an integration test with Arquillian. Sebastian Daschner wrote about it here: Testing Java EE (or Why Integration Tests Are Overrated). I also recommend writing integration tests only for things that you can't catch in unit test, or for legacy code that has too many dependencies that are too complex to be mocked out.

Is iPaaS solving the right problems? Not knowing the fundamental difference between iPaaS and iPaaS+ could cost you down the road. Brought to you in partnership with Liaison Technologies.

Topics:
java ,javaee ,arquillian ,docker ,testing

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}