OAP provides a fixture lifecycle framework, assertion helpers, and integration utilities spread across several test-support modules.
| Module | Artifact | What it provides |
|---|---|---|
oap-stdlib-test |
oap-stdlib-test |
Core fixtures, Asserts, JsonAsserts, MetricsFixture, Benchmark |
oap-application-test |
oap-application:oap-application-test |
KernelFixture — full Kernel boot |
oap-storage-mongo-test |
oap-storage:oap-storage-mongo-test |
MongoFixture — embedded MongoDB |
oap-storage-cloud-test |
oap-storage:oap-storage-cloud-test |
S3MockFixture — LocalStack S3 |
oap-template-test |
oap-formats:oap-template-test |
TemplateEngineFixture |
Fixtures are lifecycle-managed objects that set up and tear down resources around TestNG tests. Three scopes are supported:
| Scope | Lifetime |
|---|---|
METHOD (default) |
Around each test method |
CLASS |
Around the test class (once per class) |
SUITE |
Around the entire test run (once per JVM) |
Base class for all fixtures. Override before() and after() to set up and tear down resources.
public class MyServiceFixture extends AbstractFixture<MyServiceFixture> {
public MyService service;
@Override
protected void before() {
service = new MyService();
service.start();
}
@Override
protected void after() {
service.stop();
}
}| Method | Description |
|---|---|
withScope(Scope) |
Change the fixture scope (METHOD or CLASS; use Fixtures.suiteFixture() for SUITE) |
define(key, value) |
Store a named property (available as a template variable in application.conf) |
definePort(key) |
Allocate a free TCP port, store it under key, and return it |
getProperty(key) |
Retrieve a stored property |
addChild(fixture) |
Nest another fixture; its lifecycle follows the parent |
defineLocalClasspath(key, class, resource) |
Define a classpath(...) string pointing to a test resource |
defineClasspath(key, resource) |
Define a classpath(...) string from a resource location |
definePath(key, path) |
Define a path(...) string |
defineURL(key, url) |
Define a url(...) string |
TestNG listener that drives fixture lifecycle. Add @Listeners(Fixtures.class) to your test class (or extend Fixtures), then register fixtures with fixture(f) in the constructor.
@Listeners( Fixtures.class )
public class OrderServiceTest extends Fixtures {
private final TestDirectoryFixture dir = fixture( new TestDirectoryFixture() );
private final MyServiceFixture svc = fixture( new MyServiceFixture() );
@Test
public void createsOrder() {
svc.service.create( new Order( "o-1" ) );
assertThat( svc.service.find( "o-1" ) ).isPresent();
}
}Teardown runs in reverse registration order. All teardown errors are collected and re-thrown after all fixtures complete.
// Shared across all test classes in the run
private static final MyFixture shared = Fixtures.suiteFixture( new MyFixture() );Provides an isolated, temporary directory under /tmp/test/. The directory is created before the test and deleted after.
TestDirectoryFixture dir = fixture( new TestDirectoryFixture() );
// Inside the test
Path directory = dir.testDirectory(); // /tmp/test/test-<cuid>/
Path file = dir.testPath( "output/result.json" ); // creates parent dirs automatically
URL fileUrl = dir.testUrl( "output/result.json" );
// Copy test resources from classpath into the test directory
// Looks for src/test/resources/<ClassName>/ and copies its contents
dir.deployTestData( MyTest.class );
dir.deployTestData( MyTest.class, "subdir" ); // copy into a subdirectory| Variable | Value |
|---|---|
TEST_DIRECTORY |
Absolute path of the test directory |
Use in application.conf:
services.my-service.parameters.directory = ${TEST_DIRECTORY}// Wipe and recreate without ending the fixture (e.g. between test iterations)
dir.cleanTestDirectory();Directories older than 2 hours are automatically removed on each teardown pass.
Boots a full OAP Kernel from an application.conf and the module descriptors on the classpath. Use it for integration tests that need real services wired by the IoC container.
@Listeners( Fixtures.class )
public class OrderServiceTest extends Fixtures {
private final TestDirectoryFixture dir = fixture( new TestDirectoryFixture() );
private final KernelFixture kernel = fixture( new KernelFixture(
dir,
Resources.url( OrderServiceTest.class, "application.conf" ).orElseThrow()
) );
@Test
public void test() {
OrderService svc = kernel.service( "my-module", OrderService.class );
assertThat( svc ).isNotNull();
}
}| Variable | Value |
|---|---|
TEST_HTTP_PORT |
Allocated HTTP port (usable in application.conf) |
TEST_DIRECTORY |
Absolute path of the test directory |
TEST_RESOURCE_PATH |
Classpath root for the test class |
TEST_HTTP_PREFIX |
http://localhost:<TEST_HTTP_PORT> |
| Method | Description |
|---|---|
service(moduleName, Class) |
Retrieve a service by module and type |
service(moduleName, serviceName) |
Retrieve a service by module and name |
service(reference) |
Retrieve a service by <modules.mod.svc> reference |
ofClass(Class) |
Get all services of a type |
defaultHttpPort() |
Return the allocated TEST_HTTP_PORT value |
httpUrl(path) |
Build http://localhost:<port><path> |
withConfdResources(class, resource) |
Copy a classpath directory into the confd path |
withLocalConfdResources(class, resource) |
Same, with <ClassName>/ prefix |
withConfResource(class, resource) |
Copy a classpath file into the confd path |
withLocalConfResource(class, resource) |
Same, with <ClassName>/ prefix |
withProperties(map) |
Add extra kernel properties |
withFileProperties(url) |
Load extra properties from a HOCON/JSON file |
addDependency(name, fixture) |
Expose another fixture's properties to the kernel |
new KernelFixture( testDirectoryFixture, conf )
new KernelFixture( testDirectoryFixture, conf, confdPath )
new KernelFixture( testDirectoryFixture, conf, additionalModules )
new KernelFixture( testDirectoryFixture, conf, confdPath, additionalModules )Controls the Joda-Time clock for tests that depend on DateTimeUtils.currentTimeMillis().
SystemTimerFixture timer = fixture( new SystemTimerFixture() );
// Inside the test
Dates.setTimeFixed( 2024, 6, 1, 14, 30, 0 ); // freeze at 14:30:00 UTC
// ... run code that reads DateTimeUtils.currentTimeMillis() ...
Dates.incFixed( Dates.m( 5 ) ); // advance 5 minutesThe fixture resets the clock to the real system time in after(). Pass false to the constructor to skip the reset in before().
Embeds a JDK HttpServer for testing HTTP client code. A free port is allocated automatically and exposed as the HTTP_PORT fixture property.
JavaWebServerFixture server = fixture(
new JavaWebServerFixture()
.withHandler( "/api/data", exchange -> {
byte[] body = """{"ok":true}""".getBytes();
exchange.getResponseHeaders().set( "Content-Type", "application/json" );
exchange.sendResponseHeaders( 200, body.length );
exchange.getResponseBody().write( body );
exchange.close();
} )
);
// Inside the test
int port = server.getProperty( "HTTP_PORT" );| Method | Description |
|---|---|
withHandler(path, HttpHandler) |
Register a handler on a context path |
withThreads(n) |
Thread-pool size (default 10) |
withBacklog(n) |
Socket backlog (default 0) |
withWaitUntilExchangesHaveFinishedSeconds(n) |
Shutdown delay in seconds (default 1) |
Creates an isolated Micrometer SimpleMeterRegistry, adds it to the global registry before the test, and removes it after.
public class ServiceTest extends Fixtures {
private final MetricsFixture metrics = fixture( new MetricsFixture() );
@Test
public void countsRequests() {
Metrics.counter( "requests" ).increment( 3 );
metrics.assertMetric( "requests", Tags.empty() )
.isCounter()
.isEqualTo( 3.0 );
}
}assertMetric(name, tags) returns a MetricsAssertion. Call .isCounter() or .isGauge() to narrow to the appropriate type, then .isEqualTo(double) to assert the value.
Spins up an in-process MongoDB server (via de.bwaldvogel:mongo-java-server) on a free port. No Docker or external process required.
public class StorageTest extends Fixtures {
private final MongoFixture mongo = fixture( new MongoFixture( "mydb" ) );
@Test
public void insertsDocument() {
mongo.insertDocument( StorageTest.class, "items", "item.json" );
assertThat( mongo.client().getCollection( "items" ).countDocuments() ).isEqualTo( 1 );
}
}| Variable | Value |
|---|---|
PORT |
Allocated MongoDB port |
HOST |
localhost |
DATABASE |
Database name passed to the constructor |
Use in application.conf:
services.mongo.parameters.uri = "mongodb://${HOST}:${PORT}/${DATABASE}"| Method | Description |
|---|---|
createMongoClient() |
Create a MongoClient connected to the fixture server |
createMongoClient(migrationPackage, ...) |
Create a MongoClient with Mongock migration packages |
getConnectionString() |
Return the MongoDB URI for the default database |
getConnectionString(database) |
Return the MongoDB URI for a specific database |
insertDocument(contextClass, collection, resource) |
Insert one document from a classpath JSON resource |
initializeVersion(version) |
Set the stored schema version |
client() |
Return the fixture-managed MongoClient |
dropDatabase(database) |
Drop a database |
Starts a LocalStack container with S3 enabled via Testcontainers. Requires Docker.
public class CloudStorageTest extends Fixtures {
private final S3MockFixture s3 = fixture(
new S3MockFixture().withInitialBuckets( "my-bucket" )
);
@Test
public void uploadsFile() {
s3.uploadFile( "my-bucket", "key/data.json", "{}" , Map.of() );
String content = s3.readFile( "my-bucket", "key/data.json",
ContentReader.ofString(), Encoding.PLAIN );
assertThat( content ).isEqualTo( "{}" );
}
}| Variable | Value |
|---|---|
HTTP_PORT |
Allocated LocalStack HTTP port |
| Method | Description |
|---|---|
withInitialBuckets(names...) |
Create these buckets on startup |
uploadFile(bucket, key, path) |
Upload a local file |
uploadFile(bucket, key, path, tags) |
Upload a local file with S3 tags |
uploadFile(bucket, key, content, tags) |
Upload a string body with S3 tags |
readFile(bucket, key, reader, encoding) |
Read an object, decode with reader and encoding |
readTags(bucket, key) |
Return S3 tags as Map<String, String> |
headObject(bucket, key) |
Return S3 object metadata |
createFolder(bucket, path) |
Create a zero-byte application/x-directory object |
deleteAll() |
Delete all objects in all buckets |
getFileSystemConfiguration(bucket) |
Return OAP cloud-FS config pointing at the fixture endpoint |
getHttpPort() |
Return the allocated HTTP port |
Instantiates an OAP TemplateEngine with a /tmp/file-cache disk cache and a 5-day TTL.
public class TemplateTest extends Fixtures {
private final TemplateEngineFixture tmpl = fixture( new TemplateEngineFixture() );
@Test
public void rendersTemplate() {
var t = tmpl.templateEngine.getTemplate( ... );
assertThat( t.render( data ).get() ).isEqualTo( "expected" );
}
}Access the engine via the public field templateEngine.
Cross-process free port allocator. Uses a file lock at /tmp/port.lock to coordinate allocation between concurrently running test JVMs.
int port = Ports.getFreePort( MyTest.class ); // range 10 000–30 000AbstractFixture.definePort(key) calls this internally and stores the result as a fixture property.
Static assertion helpers for TestNG tests.
// Retry the assertion block until it passes or the timeout expires
Asserts.assertEventually(
Duration.ofSeconds( 5 ),
Duration.ofMillis( 100 ),
() -> assertThat( queue.size() ).isGreaterThan( 0 )
);Resources are looked up as <ClassName>/<name> relative to the test class's package.
String json = Asserts.contentOfTestResource( MyTest.class, "sample.json", Map.of( "id", "o-1" ) );
byte[] bytes = Asserts.bytesOfTestResource( MyTest.class, "sample.bin" );
Path path = Asserts.pathOfTestResource( MyTest.class, "sample.json" );
URL url = Asserts.urlOfTestResource( MyTest.class, "sample.json" );
// Unmarshal a HOCON file directly into an object
MyConfig cfg = Asserts.objectOfTestResource( MyConfig.class, MyTest.class, "config.conf" );// String assertion (triggers IntelliJ diff dialog on failure)
Asserts.assertString( actual )
.isEqualTo( expected )
.isEqualToIgnoringCase( expected )
.isEqualToLineSorting( expected ); // sort both before comparing
// File assertion
Asserts.assertFile( path )
.exists()
.hasContent( "expected text" )
.hasContent( "expected text", IoStreams.Encoding.GZIP )
.hasContentLineSorting( "sorted expected", IoStreams.Encoding.PLAIN )
.hasSize( 1024L )
.hasSameContentAs( otherPath );
// oap.util.Result assertion
Asserts.assertResult( Result.success( "value" ) ).isSuccess();
Asserts.assertResult( Result.failure( "err" ) ).isFailure();Structural JSON assertions that compare objects independently of key order.
import static oap.json.testng.JsonAsserts.assertJson;
// Key order is ignored; both sides are deeply sorted before comparison
assertJson( actual )
.isEqualTo( """{"name":"Alice","id":"1"}""" );
// Compare with a test resource file
assertJson( actual )
.isEqualTo( MyTest.class, "expected.json" );
// Map substitutions applied to the expected string before comparison
assertJson( actual )
.isEqualTo( expected, Map.of( "id", "42" ) );
// Canonical form comparison (round-trips through the Java type)
assertJson( actual )
.isEqualCanonically( MyBean.class, expected );assertJson accepts a JSON string or a Map<String, Object>.
Reflection-based parameterized test framework. Define an abstract master class with @CaseProvider methods; concrete subclasses supply case data; CaseSuite.casesOf() discovers all subclasses and assembles a TestNG data provider.
// Abstract master class
public abstract class ConversionTest {
@CaseProvider
public abstract Object[][] cases();
@Test( dataProvider = "cases" )
public void converts( CaseContext ctx, String input, String expected ) {
ctx.assertion( () -> assertThat( convert( input ) ).isEqualTo( expected ) );
}
@DataProvider
public Object[][] cases() {
return CaseSuite.casesOf( this, ConversionTest.class );
}
}
// Concrete case class
public class UpperCaseConversionTest extends ConversionTest {
@CaseProvider
public Object[][] cases() {
return new Object[][] {
CaseSuite.thecase( "hello", "HELLO" ),
CaseSuite.thecase( "world", "WORLD" )
};
}
}CaseContext.assertion(Runnable) runs the block and prepends the case class name to any assertion error message, making failures easy to locate.
Generates test-context-aware names by inspecting the call stack to find the current TestNG method.
// Inside a @Test, @BeforeMethod, @AfterMethod, @BeforeClass, etc.
String name = TestUtils.randomName( "{test_class_name}-{test_method_name}-{rand}" );| Placeholder | Replaced with |
|---|---|
{test_class_name} |
Simple name of the test class |
{test_method_name} |
Name of the @Test method (not allowed in @Before/@After) |
{rand} |
Stable 6-character random alphabetic string (same value per test execution) |
Micro-benchmark harness with TeamCity integration.
import static oap.benchmark.Benchmark.benchmark;
@Test
public void hashMapPutPerformance() {
Map<String, String> map = new ConcurrentHashMap<>();
benchmark( "put", 100_000, i -> map.put( Integer.toString( i ), "v" ) )
.warming( 1_000 ) // warm-up iterations before timing (default 1000)
.experiments( 5 ) // number of timed runs (default 5)
.threads( 4 ) // run on 4 threads simultaneously
.period( Period.seconds( 1 ) ) // rate denominator (default 1 second)
.run();
}The code lambda receives (experiment, sample) indices. Use the single-argument form i -> ... for simple cases.
benchmarking HashMapPutPerformance#put: experiment 0(4thds), 100000 samples, 45321 usec, avg time 0 usec, rate 2205022.0000 action/s
benchmarking HashMapPutPerformance#put: avg time 0 usec, avg rate 2189341.2000 action/s
Results are also reported to TeamCity as performance metrics when running in CI.
Benchmark.run() returns a Result with fields:
rate— operations perperiodtime— average time per operation in µsexperiment— label of the last (or only) experiment