OpenTelemetry SDK Testing utilities providing comprehensive testing support for OpenTelemetry Java SDK applications with AssertJ assertions, in-memory exporters, and JUnit integration.
—
Utilities for controlling time during tests and managing context storage for test isolation and deterministic behavior. These utilities enable precise control over temporal aspects of telemetry and context propagation during testing.
Controllable clock implementation that allows manipulation of time during tests for deterministic and predictable timing behavior.
@ThreadSafe
class TestClock implements Clock {
// Factory methods
static TestClock create();
static TestClock create(Instant instant);
// Time manipulation
synchronized void setTime(Instant instant);
synchronized void advance(Duration duration);
synchronized void advance(long duration, TimeUnit unit);
// Clock interface methods
synchronized long now();
synchronized long nanoTime();
}Usage examples:
// Create a test clock with current time
TestClock clock = TestClock.create();
// Create a test clock with specific time
TestClock fixedClock = TestClock.create(Instant.parse("2023-01-01T12:00:00Z"));
// Use in OpenTelemetry configuration
OpenTelemetrySdk sdk = OpenTelemetrySdk.builder()
.setTracerProvider(
SdkTracerProvider.builder()
.setClock(clock)
.build())
.build();
// Control time during test execution
Tracer tracer = sdk.getTracer("test");
// Set specific start time
clock.setTime(Instant.parse("2023-01-01T10:00:00Z"));
Span span = tracer.spanBuilder("test-span").startSpan();
// Advance time and end span
clock.advance(Duration.ofSeconds(5));
span.end();
// Verify timing
List<SpanData> spans = spanExporter.getFinishedSpanItems();
assertThat(spans.get(0))
.startsAt(Instant.parse("2023-01-01T10:00:00Z"))
.endsAt(Instant.parse("2023-01-01T10:00:05Z"));Advanced time control examples:
@Test
void testTimeBasedBehavior() {
TestClock clock = TestClock.create(Instant.parse("2023-01-01T00:00:00Z"));
// Configure service with test clock
MyTimedService service = new MyTimedService(openTelemetry, clock);
// Test behavior at specific times
service.performScheduledTask(); // Task at midnight
// Advance to next hour
clock.advance(Duration.ofHours(1));
service.performScheduledTask(); // Task at 1 AM
// Verify time-based metrics
assertThat(metricReader.collectAllMetrics())
.extracting(MetricData::getName)
.contains("scheduled.task.executions")
.hasSize(2);
}
@Test
void testTimeoutBehavior() {
TestClock clock = TestClock.create();
TimeoutService service = new TimeoutService(openTelemetry, clock);
// Start an operation with 30-second timeout
clock.setTime(Instant.parse("2023-01-01T12:00:00Z"));
service.startLongRunningOperation();
// Advance past timeout
clock.advance(Duration.ofSeconds(35));
service.checkForTimeouts();
// Verify timeout span
assertThat(spanExporter.getFinishedSpanItems())
.first()
.hasName("long.running.operation")
.hasStatusSatisfying(status ->
status.hasCode(StatusCode.ERROR)
.hasDescription("Operation timed out")
);
}
@Test
void testMetricCollectionTiming() {
TestClock clock = TestClock.create();
// Configure metric reader with test clock
InMemoryMetricReader reader = InMemoryMetricReader.builder()
.setClock(clock)
.build();
SdkMeterProvider meterProvider = SdkMeterProvider.builder()
.registerMetricReader(reader)
.setClock(clock)
.build();
Meter meter = meterProvider.get("test");
LongCounter counter = meter.counterBuilder("test.counter").build();
// Record metrics at specific times
clock.setTime(Instant.parse("2023-01-01T12:00:00Z"));
counter.add(10);
clock.advance(Duration.ofMinutes(1));
counter.add(5);
// Collect metrics and verify timestamps
Collection<MetricData> metrics = reader.collectAllMetrics();
// Verify metric timestamps align with controlled time
}Configurable context storage provider that allows switching context storage implementations during tests for testing context propagation and isolation.
class SettableContextStorageProvider implements ContextStorageProvider {
// Static configuration methods
static void setContextStorage(ContextStorage storage);
static ContextStorage getContextStorage();
// ContextStorageProvider interface method
ContextStorage get();
}Usage examples:
// Configure context storage for testing
@BeforeEach
void setUp() {
// Set up thread-local context storage for isolation
SettableContextStorageProvider.setContextStorage(
ContextStorage.defaultStorage()
);
}
@Test
void testContextPropagation() {
OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
Tracer tracer = openTelemetry.getTracer("test");
// Create parent span
Span parentSpan = tracer.spanBuilder("parent").startSpan();
try (Scope scope = parentSpan.makeCurrent()) {
// Context should propagate to child operations
MyService service = new MyService(openTelemetry);
service.performOperation(); // Should create child span
// Verify parent-child relationship
assertThat(spanExporter.getFinishedSpanItems())
.hasSize(1)
.first()
.hasParent(parentSpan);
} finally {
parentSpan.end();
}
}
@Test
void testContextIsolation() {
// Test that contexts don't leak between operations
OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
Tracer tracer = openTelemetry.getTracer("test");
// First operation with its own context
Span span1 = tracer.spanBuilder("operation-1").startSpan();
try (Scope scope1 = span1.makeCurrent()) {
performFirstOperation();
} finally {
span1.end();
}
// Second operation should not see first operation's context
Span span2 = tracer.spanBuilder("operation-2").startSpan();
try (Scope scope2 = span2.makeCurrent()) {
performSecondOperation();
} finally {
span2.end();
}
// Verify no cross-contamination
List<SpanData> spans = spanExporter.getFinishedSpanItems();
assertThat(spans).hasSize(2);
assertThat(spans.get(0)).hasNoParent();
assertThat(spans.get(1)).hasNoParent();
}
// Custom context storage for advanced testing scenarios
@Test
void testCustomContextStorage() {
// Create custom context storage that tracks access
TrackingContextStorage trackingStorage = new TrackingContextStorage();
SettableContextStorageProvider.setContextStorage(trackingStorage);
OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
Tracer tracer = openTelemetry.getTracer("test");
Span span = tracer.spanBuilder("tracked-operation").startSpan();
try (Scope scope = span.makeCurrent()) {
// Perform operations that should access context
performTrackedOperation();
} finally {
span.end();
}
// Verify context access patterns
assertThat(trackingStorage.getAccessCount()).isGreaterThan(0);
assertThat(trackingStorage.getLastAccessedContext()).isNotNull();
}Examples showing how time and context utilities work together with other testing components:
@Test
void testCompleteTemporalScenario() {
// Set up controlled time and context
TestClock clock = TestClock.create(Instant.parse("2023-01-01T09:00:00Z"));
SettableContextStorageProvider.setContextStorage(ContextStorage.defaultStorage());
// Configure OpenTelemetry with test utilities
InMemorySpanExporter spanExporter = InMemorySpanExporter.create();
InMemoryMetricReader metricReader = InMemoryMetricReader.create();
OpenTelemetrySdk sdk = OpenTelemetrySdk.builder()
.setTracerProvider(
SdkTracerProvider.builder()
.setClock(clock)
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.build())
.setMeterProvider(
SdkMeterProvider.builder()
.setClock(clock)
.registerMetricReader(metricReader)
.build())
.build();
// Simulate time-based business logic
TimedBusinessService service = new TimedBusinessService(sdk, clock);
// Execute operation at 9:00 AM
service.startDailyProcessing();
// Advance to 9:30 AM
clock.advance(Duration.ofMinutes(30));
service.performMidProcessingCheck();
// Advance to 10:00 AM
clock.advance(Duration.ofMinutes(30));
service.completeDailyProcessing();
// Verify temporal correctness
List<SpanData> spans = spanExporter.getFinishedSpanItems();
assertThat(spans)
.extracting(SpanData::getName)
.containsExactly(
"daily.processing.start",
"mid.processing.check",
"daily.processing.complete"
);
// Verify timing relationships
assertThat(spans.get(0)).startsAt(Instant.parse("2023-01-01T09:00:00Z"));
assertThat(spans.get(1)).startsAt(Instant.parse("2023-01-01T09:30:00Z"));
assertThat(spans.get(2)).startsAt(Instant.parse("2023-01-01T10:00:00Z"));
// Verify metrics collected at correct times
Collection<MetricData> metrics = metricReader.collectAllMetrics();
assertThat(metrics)
.filteredOn(metric -> "processing.duration".equals(metric.getName()))
.hasSize(1);
}
@Test
void testConcurrentContextIsolation() {
TestClock clock = TestClock.create();
// Set up concurrent context isolation
SettableContextStorageProvider.setContextStorage(
new ThreadLocalContextStorage()
);
OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
// Execute concurrent operations
CountDownLatch latch = new CountDownLatch(3);
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
final int operationId = i;
executor.submit(() -> {
try {
Tracer tracer = openTelemetry.getTracer("test");
Span span = tracer.spanBuilder("concurrent-operation-" + operationId).startSpan();
try (Scope scope = span.makeCurrent()) {
// Each thread should have isolated context
performConcurrentOperation(operationId);
Thread.sleep(100); // Simulate work
} finally {
span.end();
latch.countDown();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// Wait for completion
assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
// Verify each operation created its own span without interference
List<SpanData> spans = spanExporter.getFinishedSpanItems();
assertThat(spans).hasSize(3);
assertThat(spans).allSatisfy(span -> assertThat(span).hasNoParent());
executor.shutdown();
}// Time control interface
interface Clock {
/**
* Returns the current epoch time in milliseconds.
*/
long now();
/**
* Returns the current time in nanoseconds for high-resolution timing.
*/
long nanoTime();
}
// Context storage interfaces
interface ContextStorageProvider {
/**
* Returns the ContextStorage implementation to use.
*/
ContextStorage get();
}
interface ContextStorage {
/**
* Returns the default context storage implementation.
*/
static ContextStorage defaultStorage();
/**
* Attaches a context, returning a scope for cleanup.
*/
Scope attach(Context context);
/**
* Returns the current context.
*/
Context current();
}
// Time manipulation types
class Duration {
static Duration of(long amount, TemporalUnit unit);
static Duration ofNanos(long nanos);
static Duration ofSeconds(long seconds);
static Duration ofMinutes(long minutes);
static Duration ofHours(long hours);
static Duration ofDays(long days);
}
enum TimeUnit {
NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS;
long toNanos(long duration);
long toMicros(long duration);
long toMillis(long duration);
long toSeconds(long duration);
}
class Instant {
static Instant now();
static Instant parse(CharSequence text);
static Instant ofEpochMilli(long epochMilli);
static Instant ofEpochSecond(long epochSecond);
long toEpochMilli();
long getEpochSecond();
int getNano();
Instant plus(Duration duration);
Instant minus(Duration duration);
}
// Context propagation types
interface Context {
static Context current();
static Context root();
<T> T get(ContextKey<T> key);
<T> Context with(ContextKey<T> key, T value);
}
interface Scope extends AutoCloseable {
void close();
}
class ContextKey<T> {
static <T> ContextKey<T> named(String name);
String getName();
}// Use fixed start times for deterministic tests
TestClock clock = TestClock.create(Instant.parse("2023-01-01T00:00:00Z"));
// Advance time in meaningful increments
clock.advance(Duration.ofSeconds(1)); // For second-precision tests
clock.advance(Duration.ofMillis(100)); // For millisecond-precision tests
// Reset clock state between tests
@BeforeEach
void resetClock() {
clock.setTime(Instant.parse("2023-01-01T00:00:00Z"));
}// Ensure context cleanup in tests
@AfterEach
void cleanupContext() {
// Reset to default storage if changed
SettableContextStorageProvider.setContextStorage(ContextStorage.defaultStorage());
}
// Use try-with-resources for context scopes
try (Scope scope = span.makeCurrent()) {
// Operations within context
} // Automatic cleanupInstall with Tessl CLI
npx tessl i tessl/maven-io-opentelemetry--opentelemetry-sdk-testing