Testing Guide¶
Complete guide to testing Keycloak Events to Everywhere.
Test Types¶
Unit Tests¶
Location: src/tests/java/io/github/fortunen/kete/
Framework: JUnit 5 + AssertJ + Mockito
Purpose: Test individual components in isolation
Run:
Integration Tests¶
Purpose: Test extension with real message brokers
Uses: Testcontainers for Kafka, RabbitMQ, MQTT, etc.
Run:
End-to-End Tests¶
Location: src/tests/java/io/github/fortunen/kete/e2e/
Purpose: Test full event flow from serialization to destination delivery
Uses: Testcontainers with actual brokers
Run:
Running Tests¶
All Tests¶
Specific Test Class¶
mvn test -Dtest=io.github.fortunen.kete.provider.onEventTests
mvn test -Dtest=io.github.fortunen.kete.destinations.kafkadestination.sendTests
Specific Test Method¶
Skip Tests¶
Test Organization¶
One Method Per File Pattern¶
Each test file tests exactly ONE method from the source class:
src/tests/java/io/github/fortunen/kete/
├── provider/
│ ├── constructorTests.java # Tests Provider constructor
│ ├── onEventTests.java # Tests Provider.onEvent()
│ ├── onAdminEventTests.java # Tests Provider.onAdminEvent()
│ └── closeTests.java # Tests Provider.close()
├── providerfactory/
│ ├── initTests.java # Tests ProviderFactory.init()
│ ├── postInitTests.java # Tests ProviderFactory.postInit()
│ ├── runTests.java # Tests ProviderFactory.run()
│ └── closeTests.java # Tests ProviderFactory.close()
└── ...
Test File Naming¶
Pattern: {methodName}Tests.java
Examples:
- serializeTests.java - Tests serialize() method
- acceptTests.java - Tests accept() method
- initializeTests.java - Tests initialize() method
Overloaded Methods¶
Append parameter type to distinguish:
isEmpty_ArrayTests.java # Tests isEmpty(Object[])
isEmpty_CollectionTests.java # Tests isEmpty(Collection<?>)
isEmpty_StringTests.java # Tests isEmpty(String)
Test Structure (AAA Pattern)¶
All tests use the Arrange-Act-Assert pattern with required comments:
@Test
public void shouldDoSomethingWhenConditionMet() {
// arrange
var instance = new ClassUnderTest();
var input = "test-input";
// act
var result = instance.methodUnderTest(input);
// assert
assertThat(result).isNotNull();
assertThat(result).isEqualTo("expected");
}
Whitespace Rules¶
| Location | Rule |
|---|---|
After method opening { |
ONE blank line |
After // arrange comment |
ONE blank line |
Before // act comment |
ONE blank line |
After // act comment |
ONE blank line |
Before // assert comment |
ONE blank line |
After // assert comment |
ONE blank line |
Before method closing } |
ZERO blank lines |
Exception Testing¶
@Test
public void shouldThrowWhenConfigurationIsNull() {
// arrange
var instance = new ClassUnderTest();
// act
var thrown = catchThrowable(() -> instance.initialize(null));
// assert
assertThat(thrown)
.isInstanceOf(IllegalStateException.class)
.hasMessage("configuration is required");
}
Test Configuration¶
testcontainers.properties¶
Location: src/tests/resources/testcontainers.properties
Properties:
- reuse.enable=true - Reuse containers across test runs
- Faster test execution
- Containers persist between runs
Test Environment¶
Start Test Infrastructure¶
# Start Kafka and RabbitMQ
.\docker-infra.ps1 start
# Start Keycloak
.\docker-run.ps1
# Run tests
mvn test
Clean Test Environment¶
# Stop all
.\docker-infra.ps1 stop
docker stop keycloak-hephaestus
# Clean volumes
.\docker-infra.ps1 clean
Coverage Reports¶
Generate Coverage¶
View Reports¶
# Open in browser
start target/site/jacoco/index.html # Windows
open target/site/jacoco/index.html # macOS
xdg-open target/site/jacoco/index.html # Linux
Coverage Files¶
target/
├── jacoco.exec # Coverage data
└── site/
└── jacoco/
├── index.html # Main report
├── jacoco.xml # XML format
└── jacoco.csv # CSV format
Test Reports¶
Surefire Reports¶
Location: target/surefire-reports/
View Test Results¶
# All test results
Get-ChildItem target\surefire-reports\*.txt | ForEach-Object { Get-Content $_ }
# Failed tests only
Get-Content target\surefire-reports\*.txt | Select-String "FAILURE"
Writing Tests¶
Unit Test Example¶
@Test
void testConfiguration() {
Configuration config = new Configuration();
// Test destination names
Set<String> names = config.getDestinationNames();
assertNotNull(names);
assertTrue(names.contains("kafka-1"));
}
Integration Test Example¶
@Testcontainers
class IntegrationTest {
@Container
static KeycloakContainer keycloak = new KeycloakContainer()
.withProviders(Path.of("target/kete.jar"));
@Test
void testExtensionLoaded() {
// Test extension functionality
}
}
Playwright UI Test Example¶
@Test
void testAdminConsole() {
page.navigate("http://localhost:7070/admin");
page.fill("#username", "admin");
page.fill("#password", "admin");
page.click("button[type='submit']");
// Verify extension appears
assertTrue(page.isVisible("text=events-to-everywhere"));
}
Test Data¶
Mock Events¶
Event event = new Event();
event.setType("LOGIN");
event.setRealmId("master");
event.setUserId("user-123");
event.setTime(System.currentTimeMillis());
Map<String, String> details = new HashMap<>();
details.put("username", "testuser");
event.setDetails(details);
Mock Admin Events¶
AdminEvent adminEvent = new AdminEvent();
adminEvent.setOperationType("CREATE");
adminEvent.setResourceType("USER");
adminEvent.setRealmId("master");
Debugging Tests¶
Debug in IDE¶
IntelliJ IDEA: 1. Right-click test class 2. Select "Debug 'TestClass'" 3. Set breakpoints as needed
VS Code:
{
"type": "java",
"request": "launch",
"mainClass": "org.junit.platform.console.ConsoleLauncher",
"args": [
"--select-class", "io.github.fortunen.kete.ConfigurationTest"
]
}
Debug with Maven¶
Connect debugger to port 5005.
Verbose Output¶
Continuous Testing¶
Watch Mode¶
Fast Feedback¶
# Run only modified tests
mvn test -Dsurefire.failIfNoSpecifiedTests=false
# Parallel execution
mvn test -DforkCount=4
Test Troubleshooting¶
Tests Fail - Containers Not Starting¶
Check:
Solution:
Tests Fail - Port Conflicts¶
Check:
Solution: Stop conflicting services or change test ports.
Tests Timeout¶
Increase timeout:
Flaky Tests¶
Common causes: - Timing issues - Shared state - Resource contention
Solutions: - Add proper waits - Isolate test data - Use test containers
CI/CD Integration¶
GitHub Actions¶
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
java-version: '21'
- name: Run Tests
run: mvn clean verify
- name: Upload Coverage
uses: codecov/codecov-action@v3