The Quarkus Test Matrix: Escape Flaky Tests and Embrace Dev Services Magic
From red pills to green builds: Discover how Quarkus reinvents Java testing with Dev Services, continuous feedback, and native confidence.
You’ve seen it, haven’t you?
That moment when a perfectly green build turns blood red in CI.
“Database not available.” “Timeout connecting to Kafka.”
Tests that passed on your machine collapse in the pipeline like Agent Smith in a room full of Neos.
You start to question reality: Is it me? Is it the environment?
What if I told you the problem isn't you—it’s the illusion of control over your test infrastructure.
"You take the blue pill, your test setup stays the same, and you believe what you've always believed.
You take the red pill, you stay in QuarkusLand and I show you how deep the test automation rabbit hole goes."
Let’s take the red pill.
With Quarkus, you get:
Dev Services that launch services like PostgreSQL or Kafka without lifting a finger.
Continuous testing that re-runs your tests the moment you change your code.
Test Profiles to isolate scenarios without spaghetti configuration.
Native binary test execution. No mocks, no cheats.
Let’s jack in.
The App: People of the Resistance
You are Neo now. Your mission:
Build a Quarkus app to track your fellow rebels—Trinity, Morpheus, the whole gang.
Behind the scenes, your app talks to PostgreSQL using JPA. You’ll create endpoints, seed the database with Flyway, and test it all without any external setup.
Ready?
1. Bootstrapping the Construct
Generate your project: Think of this as jacking into the Construct:
mvn io.quarkus.platform:quarkus-maven-plugin:create \
-DprojectGroupId=com.resistance \
-DprojectArtifactId=matrix-test \
-Dextensions="rest, hibernate-orm-panache, jdbc-postgresql, flyway, rest-jackson"
cd matrix-test
Define the Entity: Person.java
import jakarta.persistence.Entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
@Entity
public class Person extends PanacheEntity {
public String name;
}
This entity represents the rebels. Each one has a name. Their ID? Assigned by the machine.
Expose the REST Endpoint: PersonResource.java
@Path("/people")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Transactional
public class PersonResource {
// Injecting the EntityManager to handle database operations
// you can also use
// PanacheRepository or PanacheEntity for simpler operations
@PersistenceContext
EntityManager em;
@POST
public Response create(Person p) {
em.persist(p);
return Response.status(Response.Status.CREATED).entity(p).build();
}
@GET
public List<Person> all() {
return em.createQuery("from Person", Person.class).getResultList();
}
}
This resource is your operator console: View and onboard rebels in the fight.
Flyway Seeds the Resistance
src/main/resources/db/migration/V1__init.sql
create table Person (id bigint not null, name varchar(255), primary key (id));
create sequence Person_SEQ start with 2 increment by 50;
INSERT INTO Person (id,name) VALUES (1,'Neo');
The first seed is planted. The One is in the system.
The Config: application.properties
quarkus.datasource.db-kind=postgresql
quarkus.hibernate-orm.database.generation=none
quarkus.flyway.migrate-at-start=true
quarkus.flyway.baseline-on-migrate=true
No JDBC URL needed. No container to run. Quarkus Dev Services acts like the Oracle and are starting what you need, when you need it.
2. Continuous Testing: Bullet Time for Devs
PersonResourceTest.java
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.hasItems;
@QuarkusTest
public class PersonResourceTest {
@Test
public void testCreateAndList() {
given()
.contentType("application/json")
.body("{\"name\":\"Trinity\"}")
.when()
.post("/people")
.then()
.statusCode(201);
given()
.contentType("application/json")
.when().get("/people")
.then()
.statusCode(200)
.body("name", hasItems("Neo", "Trinity"));
}
}
You add the test. Quarkus runs it instantly.
No rebuilds. No waiting. No Agents.
Test the Rebels
Start the dev mode:
./mvnw quarkus:dev
Then press r
.
Boom: you’re in continuous testing mode. Change your code. Change your tests. Quarkus instantly re-runs everything, like dodging bullets in slow motion.
You can also just tick the continuous testing tab in the Dev UI (http://localhost:8080/q/dev-ui/continuous-testing).
3. Test Profiles: Parallel Realities
Sometimes you want a different world. Maybe one where Flyway doesn’t run and you insert your own data.
You’re not bending the rules. You’re bending reality.
Custom Profile: TestProfileCustomData.java
Quarkus supports the idea of a test profile. If a test has a different profile to the previously run test then Quarkus will be shut down and started with the new profile before running the tests. This is obviously a bit slower, as it adds a shutdown/startup cycle to the test time, but gives a great deal of flexibility. Let’s build one that removes flyway and adds a drop-and-create table generation strategy for Hibernate.
package com.resistance;
import java.util.Map;
import io.quarkus.test.junit.QuarkusTestProfile;
public class TestProfileCustomData implements QuarkusTestProfile {
@Override
public Map<String, String> getConfigOverrides() {
Map<String, String> config = new java.util.HashMap<>();
config.put("quarkus.flyway.migrate-at-start", "false");
config.put("quarkus.hibernate-orm.database.generation", "drop-and-create");
return config;
}
}
Custom Test with Manual Seeding
package com.resistance;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import jakarta.transaction.Transactional;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.hasItem;
@QuarkusTest
@TestProfile(TestProfileCustomData.class)
public class PersonTestWithCustomData {
@BeforeEach
@Transactional
void setup() {
// empty the database before each test
Person.deleteAll();
// insert a single person with name "Morpheus"
Person person = new Person();
person.name = "Morpheus";
person.persist();
}
@Test
void testOnlyMorpheusPresent() {
given()
.when().get("/people")
.then()
.statusCode(200)
.body("name", hasItem("Morpheus"));
}
}
Test Profiles let you switch contexts like Neo switches clothes in the Matrix loading program.
4. Native Testing: No Simulation
Want to know if your app really works in production? Don’t run tests in the JVM. Run them against the native binary:
./mvnw verify -Dnative
This is the red pill all the way. Quarkus builds the app with GraalVM and runs the tests against the compiled binary.
“This isn’t the training program anymore.”
5. Dev Services Beyond PostgreSQL
Quarkus isn’t just a one-trick pony. It can launch:
Kafka (message bus of the resistance)
Redis (fast memory to outwit Agents)
MongoDB (non-relational safehouses)
Keycloak (identity for the freed minds)
Add the extension, and the Matrix configures itself.
See the full Dev Services list here:
👉 https://quarkus.io/guides/dev-services
Why This Matters
Quarkus doesn’t just help you write fast apps. It helps you trust them.
Dev Services: no more flaky test containers
Continuous Testing: real-time feedback like Oracle vision
Test Profiles: choose your reality
Native Tests: prove you're production-ready
Quarkus makes testing feel like a superpower and not a chore.
Where to Go From Here
Learn more about Quarkus and Testing
Swap PostgreSQL for Kafka and watch Dev Services go full Morpheus
Add
@InjectMock
to simulate sentinelsUse
@TestTransaction
to test rollback in bullet time
Or delete that ancient docker-compose-test.yml
.
Let Quarkus be your operator. Let go.