For one of our clients we use the Quarkus framework. Quarkus is a full-stack, Kubernetes-native Java framework, designed to work with popular Java standards, frameworks and libraries. It is possible to get into details about Quarkus, but that’s not what this post is about! This blog will tell you how to set up a simple database driven application with a full end-to-end test, using Quarkus and testcontainers.

Prerequisites

To be able to follow this how-to you’ll need the following:

  • An IDE (like IntelliJ IDEA)

  • JDK 11+

  • Maven 3.8.1+

  • Gradle 7.2

  • Have Docker installed on your machine

Bootstrap your Quarkus HelloWorld app

Use your command-prompt or your terminal application:

mvn io.quarkus.platform:quarkus-maven-plugin:2.3.1.Final:create \
  -DprojectGroupId=JDriven \
  -DprojectArtifactId=helloWorld \
  -DprojectVersion=0.0.1 \
  -DclassName="com.jdriven.hello" \
  -Dextensions="resteasy,resteasy-jackson" \
  -DbuildTool=gradle

Now import the project in your favorite editor, and you should be good to go!

Dependencies

Open the build.gradle file, and add the following dependencies:

  # We need hibernate and the jdbc driver
  implementation 'io.quarkus:quarkus-hibernate-orm'
  implementation 'io.quarkus:quarkus-jdbc-mariadb'

  # The testcontainers mariadb dependency is needed for a full integration test of the service
  testImplementation "org.testcontainers:mariadb:1.16.1"

Configuration

It’s time to set the configuration, so open up the application.properties

  # The port your application actually listens to (defaults to 8080)
  quarkus.http.port=8082

  # Default database settings
  quarkus.datasource.db-kind=mariadb
  quarkus.datasource.jdbc.driver=org.mariadb.jdbc.Driver
  quarkus.datasource.jdbc.initial-size=1
  quarkus.hibernate-orm.database.generation=none
  quarkus.datasource.username=developer
  quarkus.datasource.password=developer
  quarkus.datasource.jdbc.url=jdbc:mariadb://localhost:3306/testdb
  # This line is needed so Quarkus will know where to scan for the entities.
  quarkus.hibernate-orm.packages=com.jdriven.persistence

  # During the execution of ./gradlew quarkusdev, the following settings are used
  %dev.quarkus.datasource.url=jdbc:h2:mem:default
  %dev.quarkus.datasource.driver=org.h2.Driver
  %dev.quarkus.datasource.username=admin
  %dev.quarkus.hibernate-orm.database.generation=create
  %dev.quarkus.hibernate-orm.database.generation.create-schemas=true

  # During the execution of your tests, the following settings are used
  %test.quarkus.datasource.jdbc.driver=org.testcontainers.jdbc.ContainerDatabaseDriver
  %test.quarkus.datasource.jdbc.url=jdbc:tc:mariadb:latest:///test
  %test.quarkus.hibernate-orm.database.generation=create
  %test.quarkus.hibernate-orm.database.generation.create-schemas=true
  %test.quarkus.hibernate-orm.sql-load-script=testImport.sql

Code

As you noticed, I intend to put my entities in the persistence package, so create a new package and call it persistence.

It should be like this: …​/src/main/java/com/jdriven/persistence.

In this folder we create our 'World' class:

package com.jdriven.persistence;

import javax.persistence.*;

@Entity
@Table(name="world")
public class World {
    private Long id;
    private String worldValue;

    @Id
    @SequenceGenerator(name = "worldSeq", sequenceName = "world_id_seq", allocationSize = 1, initialValue = 1)
    @GeneratedValue(generator = "worldSeq")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(length = 100)
    public String getWorldValue() {
        return worldValue;
    }

    public void setWorldValue(String worldValue) {
        this.worldValue = worldValue;
    }
}

After this we will create our WorldService in the service package, so again create a package and call it service.

It should be like this: …​/src/main/java/com/jdriven/service.

Create the WorldService class:

package com.jdriven.service;

import com.jdriven.persistence.World;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;

@ApplicationScoped
public class WorldService {

    @Inject
    EntityManager entityManager;

    // We don't actually do anything yet with the createWorld function,
    // But it's here just for reference :)
    @Transactional
    public void createWorld(String valueOfTheWorld) {
        World world = new World();
        world.setWorldValue(valueOfTheWorld);
        entityManager.persist(world);
    }

    public String findValueOfTheWorldById(Long id) {
        World world =  entityManager.find(World.class,id);
        return (world != null) ? world.getWorldValue() : "Not found!";
    }
}

Next, we’re going to alter the hello.java entry point of the application:

package com.jdriven;

import com.jdriven.service.WorldService;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class hello {
    @Inject
    WorldService worldService;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        String worldValue = worldService.findValueOfTheWorldById(1L);
        return "Hello, " + worldValue;
    }
}

Time to test the stuff!

In the test-folder, add a resources folder.

There we create a new file called testImport.sql

INSERT INTO `world` (id, worldValue) VALUES (1, 'the value should be 42...');

This import sql file is being used by Quarkus to initialise the database. We did set this up in the application.properties using the setting: %test.quarkus.hibernate-orm.sql-load-script=testImport.sql

Let’s open up the helloTest file that was created for us, and alter it to:

package com.jdriven;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class helloTest {

    @Test
    public void testHelloEndpoint() {
        given()
          .when().get("/hello")
          .then()
             .statusCode(200)
             .body(is("Hello, the value should be 42..."));
    }
}

Time to run the test:

quarkustest
shadow-left