363 lines
10 KiB
Markdown
363 lines
10 KiB
Markdown
|
# Introduction
|
||
|
|
||
|
The Keycloak JUnit 5 test framework makes it easy to write tests for Keycloak and extensions. Behind the scenes the
|
||
|
framework handles the lifecycle of Keycloak, the database, and any injected resources such as realms and clients.
|
||
|
|
||
|
Tests simply declare what they want, including specific configuration, and the framework takes care of the rest.
|
||
|
|
||
|
|
||
|
# Writing tests
|
||
|
|
||
|
An example is better than a lot of words, so here is a very basic test:
|
||
|
|
||
|
```java
|
||
|
@KeycloakIntegrationTest
|
||
|
public class BasicTest {
|
||
|
|
||
|
@InjectRealm
|
||
|
ManagedRealm realm;
|
||
|
|
||
|
@Test
|
||
|
public void test() {
|
||
|
Assertions.assertEquals("default", realm.getName());
|
||
|
Assertions.assertEquals(0, realm.admin().users().list().size());
|
||
|
}
|
||
|
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Resource lifecycle
|
||
|
|
||
|
Managed resources can have the following life-cycles:
|
||
|
|
||
|
* Global - Shared across multiple test classes
|
||
|
* Class - Shared across multiple test methods within the same test class
|
||
|
* Method - Only used for a single test method
|
||
|
|
||
|
The framework handles the lifecycle accordingly to how it is configured in the annotation, or the default lifecycle
|
||
|
for a given resource.
|
||
|
|
||
|
For example the default lifecycle for a realm is Class, but it can be changed through the annotation:
|
||
|
|
||
|
```java
|
||
|
@InjectRealm(lifecycle = LifeCycle.METHOD)
|
||
|
ManagedRealm realm;
|
||
|
|
||
|
@Test
|
||
|
public void test() {
|
||
|
realm.admin().users().create(...);
|
||
|
}
|
||
|
|
||
|
@Test
|
||
|
public void test2() {
|
||
|
Assertions.assertEquals(0, realm.admin().users().list().size());
|
||
|
}
|
||
|
```
|
||
|
|
||
|
When the lifecycle is set to Method the realm is automatically destroyed and re-created for each test method, as seen in
|
||
|
the above example where one test method adds a user to the realm, but the user is not present in the next test.
|
||
|
|
||
|
The general recommendation is to use the Class lifecycle for realms, clients, and users. Making sure that individual test
|
||
|
methods leave the resource in a way that can be re-used. Realms for example with global lifecycle can be harder to
|
||
|
maintain as individual test classes can break other tests, but at the same time using global resources can be useful
|
||
|
as it will be more performant.
|
||
|
|
||
|
## Configuring resources
|
||
|
|
||
|
Resources are configured by declaring the required configuration through a Java class. This Java class can be an inner-class
|
||
|
if it's only used for a single test class, or can be a proper class when multiple tests share the same configuration.
|
||
|
|
||
|
For example to create a realm with a specific configuration:
|
||
|
|
||
|
```java
|
||
|
@InjectRealm(config = MyRealmConfig.class)
|
||
|
ManagedRealm realm;
|
||
|
|
||
|
static class MyRealmConfig implements RealmConfig {
|
||
|
|
||
|
@Override
|
||
|
public RealmRepresentation getRepresentation() {
|
||
|
return builder()
|
||
|
.name("myrealm")
|
||
|
.groups("group-a", "group-b")
|
||
|
.build();
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The framework will automatically re-create global resources if they don't match the required configuration. For example:
|
||
|
|
||
|
```java
|
||
|
@KeycloakIntegrationTest
|
||
|
public class Test1 {
|
||
|
|
||
|
@InjectRealm(lifecycle = LifeCycle.GLOBAL, config = MyRealmConfig.class)
|
||
|
ManagedRealm realm;
|
||
|
|
||
|
}
|
||
|
|
||
|
@KeycloakIntegrationTest
|
||
|
public class Test2 {
|
||
|
|
||
|
@InjectRealm(lifecycle = LifeCycle.GLOBAL, config = MyOtherRealm.class)
|
||
|
ManagedRealm realm;
|
||
|
|
||
|
}
|
||
|
```
|
||
|
|
||
|
In this example the realm from `Test1` would be destroyed and a new realm created for `Test2` since different
|
||
|
configuration is requested.
|
||
|
|
||
|
## Multiple instances
|
||
|
|
||
|
By default, a resource does not have a reference, and child-resources are created within parent the resource without a
|
||
|
reference. For example in the following example `userA` will be created within `realmA`:
|
||
|
|
||
|
```java
|
||
|
@InjectRealm
|
||
|
ManagedRealm realmA;
|
||
|
|
||
|
@InjectUser
|
||
|
ManagedUser userA;
|
||
|
```
|
||
|
|
||
|
If you need for instance multiple realms within a test you need to set a reference on it, and use this reference for
|
||
|
child resources:
|
||
|
|
||
|
```java
|
||
|
@InjectRealm
|
||
|
ManagedRealm realmA;
|
||
|
|
||
|
@InjectUser
|
||
|
ManagedUser userA;
|
||
|
|
||
|
@InjectRealm(ref = "realmB")
|
||
|
ManagedRealm realmB;
|
||
|
|
||
|
@InjectUser(realmRef = "realmB")
|
||
|
ManagedUser userB;
|
||
|
```
|
||
|
|
||
|
As with resources without a reference if a resource is re-used in another test class compatibility will be checked.
|
||
|
For example:
|
||
|
|
||
|
```java
|
||
|
@KeycloakIntegrationTest
|
||
|
public class Test1 {
|
||
|
@InjectRealm(lifecycle = LifeCycle.GLOBAL, ref = "realmA")
|
||
|
ManagedRealm realmA;
|
||
|
|
||
|
@InjectRealm(lifecycle = LifeCycle.GLOBAL, ref="realmB", config = MyRealmConfig.class)
|
||
|
ManagedRealm realmB;
|
||
|
}
|
||
|
|
||
|
@KeycloakIntegrationTest
|
||
|
public class Test2 {
|
||
|
@InjectRealm(lifecycle = LifeCycle.GLOBAL, ref = "realmA")
|
||
|
ManagedRealm realmA;
|
||
|
|
||
|
@InjectRealm(lifecycle = LifeCycle.GLOBAL, ref="realmB", config = MyOtherRealm.class)
|
||
|
ManagedRealm realmB;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
In the above example `realmA` will be reused both for `Test1` and `Test2`, while `realmB` will be re-created between the
|
||
|
two test classes since the required configuration differs.
|
||
|
|
||
|
## Using the Keycloak admin client
|
||
|
|
||
|
The Keycloak admin client can be injected directly, which is automatically connected to the test server:
|
||
|
|
||
|
```java
|
||
|
@InjectAdminClient
|
||
|
org.keycloak.admin.client.Keycloak keycloak;
|
||
|
|
||
|
@Test
|
||
|
public void testAdminClient() {
|
||
|
keycloak.realms().findAll();
|
||
|
}
|
||
|
```
|
||
|
|
||
|
It is also available directly for a managed resource:
|
||
|
|
||
|
```java
|
||
|
@InjectRealm
|
||
|
ManagedRealm realm;
|
||
|
|
||
|
@Test
|
||
|
public void testRealmAdmin() {
|
||
|
realm.admin().users().list();
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Using Selenium
|
||
|
|
||
|
Frequently when testing Keycloak it is required to interact with login pages, required actions, etc. through the
|
||
|
browser. This can be done in two ways, where the most convinient way is to inject a Java Page representation:
|
||
|
|
||
|
```java
|
||
|
@InjectPage
|
||
|
LoginPage loginPage;
|
||
|
|
||
|
@Test
|
||
|
public void testLogin() {
|
||
|
// Do something to open the login page
|
||
|
loginPage.fillLogin(..);
|
||
|
loginPage.submit();
|
||
|
}
|
||
|
```
|
||
|
|
||
|
An alternative approach is to inject the `WebDriver` directly:
|
||
|
|
||
|
```java
|
||
|
@InjectWebDriver
|
||
|
WebDriver webDriver;
|
||
|
|
||
|
@Test
|
||
|
public void test() {
|
||
|
webDriver.switchTo().newWindow(WindowType.TAB);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
|
||
|
## OAuth Client
|
||
|
|
||
|
A convenient way to test OAuth flows are with the OAuth Client. This provides convinient methods to perform different
|
||
|
OAuth flows, and it even automatically creates its own client within the realm. For example:
|
||
|
|
||
|
```java
|
||
|
@InjectOAuthClient
|
||
|
OAuthClient oAuthClient;
|
||
|
|
||
|
@Test
|
||
|
public void testClientCredentials() throws Exception {
|
||
|
TokenResponse tokenResponse = oAuthClient.clientCredentialGrant();
|
||
|
Assertions.assertTrue(tokenResponse.indicatesSuccess());
|
||
|
Assertions.assertNotNull(tokenResponse.toSuccessResponse().getTokens().getAccessToken());
|
||
|
}
|
||
|
```
|
||
|
|
||
|
# Running tests
|
||
|
|
||
|
Tests can be run from your favourite IDE, or from the command-line using Maven. Simply run the tests and the framework
|
||
|
does the rest.
|
||
|
|
||
|
## Configuring the test framework
|
||
|
|
||
|
When running tests there are a few things than be configured:
|
||
|
|
||
|
* Server type
|
||
|
* Database type
|
||
|
* Browser type
|
||
|
|
||
|
There are a few options on how to configure the test framework, with the following ordinal:
|
||
|
|
||
|
* System properties
|
||
|
* Environment variables
|
||
|
* `.env` file in the current working directory
|
||
|
* A properties file specified with `kc.test.config` system property or `KC_TEST_CONFIG` environment variable
|
||
|
|
||
|
### Using system properties
|
||
|
|
||
|
This is not the most convenient way as it is both cumbersome to set system properties when running tests from the IDE,
|
||
|
or when running tests using Maven.
|
||
|
|
||
|
For Maven see [Maven Surefire Plugin documentation](https://maven.apache.org/surefire/maven-surefire-plugin/examples/system-properties.html) on how to
|
||
|
set system properties when using the Surefire plugin to run tests. A brief example would look something like:
|
||
|
|
||
|
```xml
|
||
|
<build>
|
||
|
<plugins>
|
||
|
<plugin>
|
||
|
<groupId>org.apache.maven.plugins</groupId>
|
||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||
|
<configuration>
|
||
|
<systemPropertyVariables>
|
||
|
<kc.test.browser>firefox</kc.test.browser>
|
||
|
</systemPropertyVariables>
|
||
|
</configuration>
|
||
|
</plugin>
|
||
|
</plugins>
|
||
|
</build>
|
||
|
```
|
||
|
|
||
|
### Using environment variables
|
||
|
|
||
|
When running tests from the CLI using environment variables is the recommended way. For example:
|
||
|
|
||
|
```shell
|
||
|
KC_TEST_BROWSER=firefox mvn test
|
||
|
```
|
||
|
|
||
|
As with system properties, using environment variables within the IDE can be cumbersome.
|
||
|
|
||
|
### Using `.env` file
|
||
|
|
||
|
When running tests from an IDE using the `.env` file is very convinient, especially as this can be added to `.gitignore`
|
||
|
allowing developers to quickly have their own personal preference when running tests.
|
||
|
|
||
|
Example `.env` file:
|
||
|
|
||
|
```
|
||
|
KC_TEST_BROWSER=firefox
|
||
|
```
|
||
|
|
||
|
### Using a properties file
|
||
|
|
||
|
Using a property file allows creating a set of configuration which can be commited to a Git repository to be shareable.
|
||
|
|
||
|
For example create the file `/path/mytestconfig.properties` with the following contents:
|
||
|
|
||
|
```
|
||
|
kc.test.browser=firefox
|
||
|
kc.test.server=remote
|
||
|
```
|
||
|
|
||
|
Then run tests with:
|
||
|
|
||
|
```shell
|
||
|
KC_TEST_CONFIG=/path/mytestconfig.properties mvn test
|
||
|
```
|
||
|
|
||
|
## Config options
|
||
|
|
||
|
### Server
|
||
|
|
||
|
Option: `kc.test.server` / `KC_TEST_SERVER`
|
||
|
|
||
|
Valid values:
|
||
|
|
||
|
| Value | Description |
|
||
|
|--------------|--------------------------------------------------------------------------------------------------------|
|
||
|
| distribution | Runs the full distribution of Keycloak in a separate JVM process |
|
||
|
| embedded | Runs a Keycloak server embedded in the same JVM process |
|
||
|
| remote | Connects to a remote Keycloak server. Requires manually configuring the server as needed for the test. |
|
||
|
|
||
|
### Database
|
||
|
|
||
|
Option: `kc.test.database` / `KC_TEST_DATABASE`
|
||
|
|
||
|
Valid values:
|
||
|
|
||
|
| Value | Description |
|
||
|
|----------|-----------------------------------------|
|
||
|
| dev-file | H2 database with a file for persistence |
|
||
|
| dev-mem | In-memory H2 database |
|
||
|
| mariadb | MariaDB test container |
|
||
|
| mssql | Microsoft SQL Server test container |
|
||
|
| mysql | MySQL test container |
|
||
|
| postgres | PostgreSQL test container |
|
||
|
|
||
|
### Browser
|
||
|
|
||
|
Option: `kc.test.broser` / `KC_TEST_BROWSER`
|
||
|
|
||
|
Valid values:
|
||
|
|
||
|
| Value | Description |
|
||
|
|------------------|------------------------------|
|
||
|
| chrome | Chrome WebDriver |
|
||
|
| chrome-headless | Chrome WebDriver without UI |
|
||
|
| firefox | Firefox WebDriver |
|
||
|
| firefox-headless | Firefox WebDriver without UI |
|