DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • A Guide for Deploying .NET 10 Applications Using Docker's New Workflow
  • A Beginner's Guide to Essential Commands to Fix Container Setup Issues
  • Docker Offload: One of the Best Features for AI Workloads
  • A Guide to Container Runtimes

Trending

  • Architecting an Embedded Efficiency Layer: A Platform Deep Dive into Day-Two Operational Tuning
  • A Walk-Through of the DZone Article Editor
  • How to Write for DZone Publications: Trend Reports and Refcards
  • Docker Hardened Images Are Free Now — Here's What You Still Need to Build
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Testing Java EE Using Docker

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.

By 
Kai Winter user avatar
Kai Winter
·
Updated Aug. 05, 16 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
14.2K Views

Join the DZone community and get the full member experience.

Join For Free

This article was updated in March 2023 for Java 17, Wildfly 27, Hibernate 6, JUnit 5, and Testcontainers 1.17.6.

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 its 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/wildfly27-mariadb, available on GitHub). Wildfly is configured to use 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 the database schema.
  4. Arquillian deploys the result of the @Deployment method.
  5. Arquillian runs every @Test method.
  6. Each @Testmethod inserts its test data by DbUnit, calls the 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. But we can register a org.jboss.arquillian.core.spi.LoadableExtension service (via META-INF/services), which can register a listener in the configuration process.

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

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


Test Example

The test looks like a common Arquillian test:

Java
 
@ExtendWith(ArquillianExtension.class)
class UserServiceTest {

    @Inject
    private UserService userService;

    @PersistenceContext
    private EntityManager entityManager;

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

    @Test
    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 to 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 has to set the DOCKER_HOST environment variable and can run integration tests. 

Benefits

  • Reduced setup time for new developers.
  • Test running independently from local configuration.
  • Always an empty database without side effects.
  • Implicitly tested data model and migration scripts.
  • Multiple test classes can run concurrently, and each runs in its own Docker container.

Links

  • The complete example
  • Testcontainers documentation

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 using what I described here as an "always-to-be-used" solution. You should always prefer a 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 tests or for legacy code that has too many dependencies that are too complex to be mocked out.

Docker (software) unit test Java EE WildFly Container integration test

Published at DZone with permission of Kai Winter. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • A Guide for Deploying .NET 10 Applications Using Docker's New Workflow
  • A Beginner's Guide to Essential Commands to Fix Container Setup Issues
  • Docker Offload: One of the Best Features for AI Workloads
  • A Guide to Container Runtimes

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook