This document explains the telemetry testing approach used in the reference application to validate OpenTelemetry data export.
The TelemetryTest class demonstrates how to test that your application correctly generates and exports OpenTelemetry telemetry data. This approach is based on the telemetry-testing example in the repository.
- Uses MockServer to simulate an OTLP collector
- Listens on port 4318 (default OTLP HTTP endpoint)
- Captures all OTLP requests sent by the OpenTelemetry Java Agent
The test suite is configured to:
- Run with OpenTelemetry Java Agent attached (
-javaagent) - Export telemetry using
http/protobufprotocol - Set faster metric export interval (5 seconds vs default 60 seconds)
- Suppress MockServer logging to avoid circular telemetry
Uses OpenTelemetry protocol buffers to parse captured requests:
ExportTraceServiceRequestfor traces/spansExportMetricsServiceRequestfor metrics- Direct access to span attributes, events, and metric values
- HTTP spans: Automatic instrumentation spans (e.g.,
GET /rolldice) - Custom spans: Manual spans created in application code (e.g.,
roll-dice,fibonacci-calculation) - Span hierarchy: Parent-child relationships between spans
- Attributes: Custom attributes like
dice.player,fibonacci.n - Events: Custom events like
dice-rolled,fibonacci-calculated - Error handling: Exception recording and error status
- Custom counters:
dice_rolls_total,fibonacci_calculations_total - Custom timers:
dice_roll_duration_seconds,fibonacci_duration_seconds - Micrometer integration: Metrics created via Micrometer and exported via OpenTelemetry
- Cross-cutting data: Player names, request types
- Propagation: Baggage values accessible across span boundaries
@Test
public void testDiceRollTelemetry() {
// Call endpoint
template.getForEntity("/rolldice", String.class);
// Verify spans are created
await().untilAsserted(() -> {
var spans = extractSpansFromRequests(requests);
assertThat(spans)
.extracting(Span::getName)
.contains("GET /rolldice", "roll-dice", "roll-single-die");
});
}- Multiple operations: Testing endpoints that create multiple spans
- Parameterized requests: Verifying span attributes contain request parameters
- Error conditions: Testing that exceptions are properly recorded
- Performance scenarios: Large computations that generate multiple events
jvmArgs = listOf(
"-javaagent:${agentJarPath}",
"-Dotel.metric.export.interval=5000",
"-Dotel.exporter.otlp.protocol=http/protobuf",
"-Dmockserver.logLevel=off"
)private List<Span> extractSpansFromRequests(HttpRequest[] requests) {
return Arrays.stream(requests)
.map(HttpRequest::getBody)
.flatMap(body -> getExportTraceServiceRequest(body).stream())
.flatMap(r -> r.getResourceSpansList().stream())
.flatMap(r -> r.getScopeSpansList().stream())
.flatMap(r -> r.getSpansList().stream())
.collect(Collectors.toList());
}- End-to-end verification: Tests actual OTLP export, not just in-memory data
- Protocol-level testing: Uses the same protobuf format as real collectors
- Realistic conditions: Tests with the actual Java Agent configuration
- Regression testing: Catch telemetry regressions before production
- Documentation: Tests serve as living examples of expected telemetry
- Debugging: Easy to inspect actual telemetry data during development
# Run all tests (including telemetry tests)
../gradlew test
# Run only telemetry tests
../gradlew test --tests "TelemetryTest"
# Run specific test method
../gradlew test --tests "TelemetryTest.testDiceRollTelemetry"- Port conflicts: Ensure port 4318 is available
- Timing issues: Use appropriate
await()timeouts for telemetry export - Agent not loaded: Verify Java Agent is properly attached in test configuration
- Network issues: MockServer requires localhost connectivity
- Enable debug logging:
-Dotel.javaagent.debug=true - Inspect raw requests: Log
request.getBodyAsString()before parsing - Check span timing: Some spans may export in separate batches
- Verify test isolation: Each test should reset MockServer expectations