Like JUnit, but aimed at System Integration Testing.
An annotation-driven system integration testing library for Java 21 under the no.kompilator.situs namespace. Define SIT suites as plain Java classes, run them on demand via a REST API or programmatically, and get structured reports in JUnit XML, Open Test Reporting XML, or JSON.
Suites run at runtime in production-like environments instead of only at build time. They support Spring dependency injection, parallel execution, timeouts, delays, retries, and deterministic ordering.
How the project is split across core, plugins, and examples.
.
├── situs/ Core library - annotations, engine, Spring integration
├── plugins/ Ready-made plugins (reporting: JUnit XML, OTR XML, JSON)
├── java-spring-boot-sample-app/ Java Spring Boot example using the library
└── kotlin-spring-boot-sample-app/ Kotlin Spring Boot example using the libraryMaven coordinates that are currently published.
Core artifacts, plugins, and sample applications.
| Module | Artifact | Description |
|---|---|---|
| situs | no.kompilator:situs | SIT annotations, execution engine, and HTTP API |
| plugins | no.kompilator:plugins | Reporting plugin - writes structured test reports |
| java-spring-boot-sample-app | - | Java sample app (not published) |
| kotlin-spring-boot-sample-app | - | Kotlin sample app (not published) |
Build against these packages only.
These may change without notice.
Quick start
Start with the core artifact, define suites as plain Java classes, and expose them through Spring Boot when needed.
// build.gradle.kts
dependencies {
implementation("no.kompilator:situs:2.0.0")
// Optional - adds structured report writing (pulls in situs transitively)
implementation("no.kompilator:plugins:2.0.0")
}@TestSuite(name = "CalculatorTestSuite", description = "Tests for Calculator")
public class CalculatorTestSuite {
@Test(name = "addition", description = "2 + 3 should equal 5", order = 1)
public void testAddition() {
assertThat(2 + 3).isEqualTo(5);
}
@Test(name = "divisionByZero", timeoutMs = 500, order = 2)
public void testDivisionByZero() {
assertThatThrownBy(() -> 1 / 0)
.isInstanceOf(ArithmeticException.class);
}
}@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
testframework.scan-packages=com.example.tests Spring Boot auto-configuration is enabled automatically when the library is on the classpath. @EnableRuntimeTests is only needed for explicit opt-in in non-Boot Spring applications.
Useful Spring properties
# List all discovered suites
curl http://localhost:8080/api/test-framework/suites
# Start a suite run (async)
curl -X POST http://localhost:8080/api/test-framework/suites/CalculatorTestSuite/run
# -> {"runId":"abc-123"}
# Poll until COMPLETED
curl http://localhost:8080/api/test-framework/runs/abc-123/status
# Cancel a running suite
curl -X POST http://localhost:8080/api/test-framework/runs/abc-123/cancelCore annotations and operational behavior.
| Feature | Annotation / API |
|---|---|
| Define a test suite | @TestSuite |
| Define a test method | @Test |
| Setup / teardown | @BeforeAll, @AfterAll, @BeforeEach, @AfterEach |
| Parallel execution | @TestSuite(parallel = true) |
| Deterministic ordering | order = ... on @Test, @BeforeAll, @BeforeEach, @AfterEach, @AfterAll |
| Timeout per test | @Test(timeoutMs = 500) |
| Delay before test | @Test(delayMs = 300) |
| Retry on failure | @Test(retries = 2) |
| Spring DI in suites | Annotate suite with @Component |
| Auto-discovery | Package-scoped scan via testframework.scan-packages |
| HTTP API | Built-in REST controller via Spring auto-configuration |
| Run cancellation | POST /api/test-framework/runs/{runId}/cancel |
| Structured reports | ReportingPlugin - JUnit XML, OTR XML, JSON |
| Incremental plugin hook | SuiteRunListener#onTestCompleted(...) |
Annotate the suite with @Component and inject dependencies through the constructor.
@Component
@TestSuite(name = "PaymentTestSuite")
public class PaymentTestSuite {
private final PaymentService paymentService;
public PaymentTestSuite(PaymentService paymentService) {
this.paymentService = paymentService;
}
@Test(name = "chargeSucceeds")
public void chargeSucceeds() {
assertThat(paymentService.charge(100)).isTrue();
}
}Use kotlin("plugin.spring") so @Component suites stay open for Spring proxying.
@Component
@TestSuite(name = "CalculatorTestSuite", description = "Tests for Calculator")
class CalculatorTestSuite(private val calculator: Calculator) {
@Test(name = "addition")
fun testAddition() {
assertThat(calculator.add(2, 3)).isEqualTo(5)
}
}Automatic report writing or manual listener registration.
dependencies {
implementation("no.kompilator:plugins:2.0.0")
} ReportingPlugin reporter = ReportingPlugin.builder()
.outputDir(Path.of("build/test-reports"))
.format(ReportFormat.JUNIT_XML)
.format(ReportFormat.JSON)
.build();
testFrameworkService.addListener(reporter); Reports are written automatically after every suite run.
Plugins can also observe per-test progress through SuiteRunListener#onTestCompleted(...).
Explicit progress and timing fields for long-running suites.
The framework fails fast during startup or registration when these conditions are violated.
Execution order stays predictable.
Release, build, and test
Local publishing, parallel verification, and release publishing are all exposed through the Gradle wrapper.
Local publish
./situs/gradlew publishAllToMavenLocalParallel multi-module verification
./situs/gradlew testAll./situs/gradlew buildAll Project-level parallel execution is enabled in gradle.properties, so independent subprojects run concurrently where Gradle can schedule them safely.
Remote publish credentials
Equivalent environment variables
./situs/gradlew publishReleaseRoot release tasks
GitHub Actions release flow
Build everything from the repo root
./situs/gradlew --project-dir . buildBuild a specific module
./situs/gradlew :situs:build./situs/gradlew :plugins:buildRun tests
./situs/gradlew :situs:test./situs/gradlew :plugins:test