A helper library for integration testing with Keycloak using Testcontainers. Provides methods to manage realms, clients, users, and obtain tokens via Keycloak Admin REST API.
$ dotnet add package EvSoftware.Infrastructure.Tests.KeycloakThis library provides a helper class (KeycloakHelper) designed to simplify integration testing of .NET applications secured by Keycloak, particularly when using Testcontainers to manage the Keycloak instance.
It allows you to programmatically interact with the Keycloak Admin REST API within your test setup (e.g., xUnit fixtures) to:
This avoids the need for static realm import files and makes test setup more dynamic and maintainable directly within your C# test code.
dotnet add package EvSoftware.Infrastructure.Tests.Keycloak
Typically, you would use this helper within an xUnit test fixture (IAsyncLifetime) that manages a Keycloak Testcontainer.
1. Setup Keycloak Testcontainer Fixture:
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using EvSoftware.Infrastructure.Tests.Keycloak;
using System.Threading.Tasks;
using Xunit;
// Example Fixture (like KeycloakFixture.cs in integration tests)
public class MyKeycloakTestFixture : IAsyncLifetime
{
private readonly IContainer _keycloakContainer;
public KeycloakHelper KeycloakHelper { get; private set; } = null!;
public string KeycloakBaseUrl { get; private set; } = null!;
private const string KeycloakImage = "quay.io/keycloak/keycloak:22.0.1"; // Or desired version
private const string AdminUser = "admin";
private const string AdminPassword = "admin";
public MyKeycloakTestFixture()
{
_keycloakContainer = new ContainerBuilder()
.WithImage(KeycloakImage)
.WithPortBinding(8080, true) // Map to random available host port
.WithEnvironment("KEYCLOAK_ADMIN", AdminUser)
.WithEnvironment("KEYCLOAK_ADMIN_PASSWORD", AdminPassword)
.WithCommand("start-dev") // Start in dev mode (HTTP)
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(8080))
.Build();
}
public async Task InitializeAsync()
{
await _keycloakContainer.StartAsync();
KeycloakBaseUrl = $"http://{_keycloakContainer.Hostname}:{_keycloakContainer.GetMappedPublicPort(8080)}";
KeycloakHelper = new KeycloakHelper(KeycloakBaseUrl, AdminUser, AdminPassword);
}
public async Task DisposeAsync()
{
KeycloakHelper?.Dispose();
if (_keycloakContainer != null)
{
await _keycloakContainer.StopAsync();
await _keycloakContainer.DisposeAsync();
}
}
}
// Define a collection fixture
[CollectionDefinition("MyKeycloakTests")]
public class KeycloakCollection : ICollectionFixture<MyKeycloakTestFixture> { }
2. Use Helper in Tests:
Inject the fixture into your test class and use the KeycloakHelper instance.
using EvSoftware.Infrastructure.Tests.Keycloak.Representations;
using System.Threading.Tasks;
using Xunit;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net;
[Collection("MyKeycloakTests")]
public class MyApiIntegrationTests
{
private readonly MyKeycloakTestFixture _fixture;
private readonly KeycloakHelper _keycloakHelper;
private readonly HttpClient _apiClient; // Your API client
public MyApiIntegrationTests(MyKeycloakTestFixture fixture)
{
_fixture = fixture;
_keycloakHelper = fixture.KeycloakHelper;
_apiClient = new HttpClient { BaseAddress = new Uri("http://localhost:5000") }; // Example API URL
}
[Fact]
public async Task ProtectedEndpoint_ShouldReturnOk_WithValidToken()
{
// Arrange: Set up Keycloak using the helper
var realmName = "test-realm";
var clientId = "test-api-client";
var username = "test-user";
var password = "password123";
await _keycloakHelper.CreateRealmAsync(new RealmRepresentation { Realm = realmName, Enabled = true });
await _keycloakHelper.CreateClientAsync(realmName, new ClientRepresentation
{
ClientId = clientId,
PublicClient = true, // Assuming public client for simplicity
DirectAccessGrantsEnabled = true // Needed for password grant
});
await _keycloakHelper.CreateUserAsync(realmName, new UserRepresentation
{
Username = username,
Credentials = new[] { new CredentialRepresentation { Type = "password", Value = password } }
});
// Get token
string accessToken = await _keycloakHelper.GetAccessTokenAsync(realmName, clientId, username, password);
// Act: Call protected API endpoint
_apiClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _apiClient.GetAsync("/api/protected");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
The library includes basic Data Transfer Object (DTO) classes under the EvSoftware.Infrastructure.Tests.Keycloak.Representations namespace to represent Keycloak entities:
RealmRepresentationClientRepresentationUserRepresentationCredentialRepresentationThese can be used when calling the KeycloakHelper methods.
Contributions are welcome! Please feel free to open an issue or submit a pull request.
This project is licensed under the MIT License - see the LICENSE file for details (assuming you add one).