Programmatic Keycloak Configuration for Quarkus Integration Tests
Quarkus offers an incredible testing framework, and its devservices
feature is fantastic for getting applications up and running quickly. However, when your testing scenarios demand more intricate setups—like custom Keycloak realms and dynamic client configurations—you may find that a more hands-on approach is beneficial.
While Quarkus provides powerful defaults, advanced use cases sometimes require more granular control. For instance, you might encounter challenges when trying to override the default realm or find that properties like quarkus.keycloak.devservices.realm-path
don’t behave as expected in all situations.
To gain full control and ensure a predictable test environment, let’s walk through how to build a custom test setup using your own QuarkusTestResourceLifecycleManager
. 🔧
The Strategy: A Custom Test Resource
The core of this solution is to create our own implementation of QuarkusTestResourceLifecycleManager
. This gives us complete authority over starting, configuring, and stopping a Keycloak instance for our tests.
Step 1: Add the Test Dependency
First, ensure your pom.xml
includes the quarkus-test-keycloak-server
dependency. This pulls in the necessary classes like KeycloakContainer
and KeycloakTestClient
.
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-keycloak-server</artifactId>
<scope>test</scope>
</dependency>
Step 2: Configure the Surefire Plugin
To make the setup more flexible, you can use the maven-surefire-plugin
to pass system properties to your tests, such as specifying a particular Keycloak Docker image.
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
<keycloak.docker.image>quay.io/keycloak/keycloak:24.0.4</keycloak.docker.image>
</systemPropertyVariables>
</configuration>
</plugin>
Step 3: Implement the Custom Test Resource
This is where you take full control. The custom resource starts a KeycloakContainer
and then uses KeycloakTestClient
to programmatically configure it. A key advantage here is the ability to load a realm from a JSON file and modify it dynamically—in this case, setting the client secret—before the realm is created.
public class MyKeycloakTestResource implements QuarkusTestResourceLifecycleManager {
private KeycloakContainer keycloak;
@Override
public Map<String, String> start() {
keycloak = new KeycloakContainer()
.withUseHttps(false);
keycloak.start();
final String path = MyResourceTest.class.getResource("/my-realm.json").getPath();
KeycloakTestClient keycloakClient = new KeycloakTestClient(keycloak.getAuthServerUrl());
// Load realm representation from a file
final RealmRepresentation realmRepresentation = keycloakClient.readRealmFile(path);
// Dynamically modify the client secret before creation
realmRepresentation.getClients().stream().filter(c -> c.getClientId().equals("my-service"))
.findFirst().get().setSecret("secret");
keycloakClient.createRealm(realmRepresentation);
// Return the configuration properties to be injected into the application
Map<String, String> conf = new HashMap<>();
conf.put("quarkus.oidc.auth-server-url", String.format("%s/realms/%s", keycloak.getAuthServerUrl(), "my-realm-name"));
conf.put("quarkus.oidc.credentials.secret", "secret");
conf.put("quarkus.oidc.client-id", "my-service");
return conf;
}
@Override
public void stop() {
if (keycloak != null) {
keycloak.stop();
}
}
}
Note: make sure to have the realm configuration file (i.e.: my-realm.json
in the class path, such as in src/test/resources
).
Step 4: Write and Annotate the Test
Finally, annotate your test class with @QuarkusTestResource
to activate your custom manager. Your test can then use KeycloakTestClient
to fetch a valid token for the service and user you configured in your realm file.
@QuarkusTest
@QuarkusTestResource(MyKeycloakTestResource.class)
public class MyResourceTest {
private String getAccessToken() {
// NOTE: The server URL for KeycloakTestClient is automatically
// configured by the Quarkus test framework when using test resources.
KeycloakTestClient keycloakClient = new KeycloakTestClient();
return keycloakClient.getAccessToken("my-test-user", "password", "my-service");
}
@Test
public void testPingServiceSuccessfully() {
final String accessToken = getAccessToken();
Assertions.assertNotNull(accessToken);
given()
.header("Content-Type", MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + accessToken)
.when().post("/api/v1/my/restricted/api/ping")
.then()
.statusCode(Response.Status.OK.getStatusCode());
}
}
Conclusion
While Quarkus defaults provide an excellent on-ramp, this custom QuarkusTestResourceLifecycleManager
approach unlocks the power and flexibility needed for complex integration tests. It gives you predictable, fine-grained control over your security test infrastructure, enabling you to validate sophisticated configurations with confidence.