Integration testing in Quarkus
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: