In this bonus lab we'll see how we can leverage Testcontainers and Keycloak Testcontainer to create a client-side end2end test for our OAuth 2.0 and OpenID Connect 1.0 compliant Resource Server.
The annotation @Testcontainers scans and configures all testcontainers marked with the other annotation @Container.
Then we create an instance of KeycloakContainer that loads the same realm configuration from keycloak_realm_workshop.json as used in the real Keycloak instance.
In the setup() operation we retrieve the required token endpoint url by retrieving the base url via keycloak.getAuthServerUrl()
packagecom.example.library.server.api;//...importdasniko.testcontainers.keycloak.KeycloakContainer;importorg.testcontainers.junit.jupiter.Container;importorg.testcontainers.junit.jupiter.Testcontainers;//...@Testcontainers@SpringBootTest(webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)@DirtiesContext@DisplayName("Verify book api")@Tag("end2end")classBookApiEnd2EndTest { @ContainerprivatestaticfinalKeycloakContainer keycloak =newKeycloakContainer().withRealmImportFile("keycloak_realm_workshop.json").withEnv("DB_VENDOR","h2"); @LocalServerPortprivateint port;privateString authServerUrl; @BeforeEachvoidsetup() { authServerUrl =keycloak.getAuthServerUrl() +"/realms/workshop/protocol/openid-connect/token";RestAssured.baseURI="http://localhost";RestAssured.port= port; } @Test @DisplayName("get list of books")voidverifyGetBooks() {String token =getToken();given().header("Authorization","Bearer "+ token).when().get("/library-server/books").then().statusCode(200); }//...}
With this code in place you should already be able to run the tests. You will notice that running the tests will last some time due to starting the Keycloak docker container first. After the container started successfully the test cases will run. Here you will recognize that the test for the happy path is still failing. This is caused by the wrong JWT issuer claim. The Keycloak testcontainer runs at a different port than the Keycloak from previous labs. The issuer claim has changed as well.
No worries, we will change this in the next section.
Step 3: Reconfigure the Issuer Claim for JWT
Open again the same test class and change the setup() operation as shown here:
To change the issuer and jwks uri, we dynamically override the oauth2 resource server configuration using DynamicPropertyRegistry instance, and the @DynamicPropertySource spring test support.
Now we can just set the issuer to the configured value of the Keycloak testcontainer.
Now the testing class should be complete and look like the following one:
Now you can run the tests and all tests should run fine and report a green status. Please also notice that the test is tagged with @Tag("end2end"). This way you can for example exclude such long-running tests from the regular build and instead only run these as part of a nightly build.
Actually the gradle build excludes this test here as well using the following additional snippet in the build.gradle file:
test { useJUnitPlatform { excludeTags 'end2end' }}
This ends this bonus lab. If you like the approach with the Testcontainers then you may look for other supported testcontainers like for example databases (this is also a great possibility to test against the real database instead of simulating this using a H2 in-memory database).