CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-opentelemetry--opentelemetry-sdk-testing

OpenTelemetry SDK Testing utilities providing comprehensive testing support for OpenTelemetry Java SDK applications with AssertJ assertions, in-memory exporters, and JUnit integration.

Pending
Overview
Eval results
Files

junit-integration.mddocs/

JUnit Integration

JUnit 4 and JUnit 5 extensions that automatically set up OpenTelemetry SDK for testing with in-memory exporters and convenient access to collected telemetry data. These extensions handle the lifecycle of OpenTelemetry configuration and provide easy access to captured spans, metrics, and logs.

Capabilities

JUnit 5 Extension

JUnit 5 extension that configures OpenTelemetry SDK with in-memory exporters and provides convenient access to collected telemetry data.

class OpenTelemetryExtension implements BeforeEachCallback, BeforeAllCallback, AfterAllCallback {
    // Factory method
    static OpenTelemetryExtension create();
    
    // OpenTelemetry access
    OpenTelemetry getOpenTelemetry();
    
    // Collected data access
    List<SpanData> getSpans();
    List<MetricData> getMetrics();
    List<LogRecordData> getLogRecords();
    
    // Assertion helper
    TracesAssert assertTraces();
    
    // Data management
    void clearSpans();
    void clearMetrics();
    void clearLogRecords();
}

Usage examples:

import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.*;

class MyServiceTest {

    @RegisterExtension
    static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create();

    @Test
    void testServiceOperation() {
        // Get the configured OpenTelemetry instance
        OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
        
        // Initialize your service with the test OpenTelemetry
        MyService service = new MyService(openTelemetry);
        
        // Execute the operation that should create telemetry
        service.performOperation("test-input");
        
        // Validate collected spans
        assertThat(otelTesting.getSpans())
            .hasSize(2)
            .satisfiesExactly(
                span -> assertThat(span)
                    .hasName("service.operation")
                    .hasAttribute(AttributeKey.stringKey("input"), "test-input"),
                span -> assertThat(span)
                    .hasName("database.query")
                    .hasKind(SpanKind.CLIENT)
            );
        
        // Validate collected metrics
        assertThat(otelTesting.getMetrics())
            .hasSize(1)
            .first()
            .hasName("operation.duration")
            .hasDoubleGaugeSatisfying(gauge ->
                gauge.hasPointsSatisfying(point ->
                    point.hasValue(greaterThan(0.0))
                )
            );
        
        // Validate collected logs
        assertThat(otelTesting.getLogRecords())
            .hasSize(1)
            .first()
            .hasBody("Operation completed successfully")
            .hasSeverity(Severity.INFO);
    }

    @Test
    void testWithTraceAssertions() {
        OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
        MyService service = new MyService(openTelemetry);
        
        service.performNestedOperations();
        
        // Use the built-in trace assertions
        otelTesting.assertTraces()
            .hasTracesSatisfyingExactly(
                trace -> trace.hasSpansSatisfyingExactly(
                    span -> assertThat(span).hasName("parent.operation"),
                    span -> assertThat(span).hasName("child.operation.1"),
                    span -> assertThat(span).hasName("child.operation.2")
                )
            );
    }
    
    @Test
    void testDataClearing() {
        OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
        MyService service = new MyService(openTelemetry);
        
        // First operation
        service.performOperation("first");
        assertThat(otelTesting.getSpans()).hasSize(1);
        
        // Clear data and perform second operation
        otelTesting.clearSpans();
        service.performOperation("second");
        assertThat(otelTesting.getSpans()).hasSize(1);
        assertThat(otelTesting.getSpans().get(0))
            .hasAttribute(AttributeKey.stringKey("input"), "second");
    }
}

JUnit 4 Rule

JUnit 4 rule that provides the same functionality as the JUnit 5 extension for projects using JUnit 4.

class OpenTelemetryRule extends ExternalResource {
    // Factory method
    static OpenTelemetryRule create();
    
    // OpenTelemetry access (identical to JUnit 5 extension)
    OpenTelemetry getOpenTelemetry();
    
    // Collected data access
    List<SpanData> getSpans();
    List<MetricData> getMetrics();
    List<LogRecordData> getLogRecords();
    
    // Data management
    void clearSpans();
    void clearMetrics();
    void clearLogRecords();
}

Usage examples:

import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule;
import org.junit.Rule;
import org.junit.Test;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.*;

public class MyServiceTest {

    @Rule
    public final OpenTelemetryRule otelTesting = OpenTelemetryRule.create();

    @Test
    public void testServiceOperation() {
        // Get the configured OpenTelemetry instance
        OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
        
        // Initialize your service with the test OpenTelemetry
        MyService service = new MyService(openTelemetry);
        
        // Execute the operation
        service.performOperation("test-input");
        
        // Validate collected data
        assertThat(otelTesting.getSpans())
            .hasSize(1)
            .first()
            .hasName("service.operation")
            .hasAttribute(AttributeKey.stringKey("input"), "test-input");
    }

    @Test
    public void testMultipleOperations() {
        OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
        MyService service = new MyService(openTelemetry);
        
        // Perform multiple operations
        service.performOperation("first");
        service.performOperation("second");
        
        // Validate all collected spans
        assertThat(otelTesting.getSpans())
            .hasSize(2)
            .extracting(SpanData::getName)
            .containsExactly("service.operation", "service.operation");
        
        // Validate attributes of specific spans
        assertThat(otelTesting.getSpans().get(0))
            .hasAttribute(AttributeKey.stringKey("input"), "first");
        assertThat(otelTesting.getSpans().get(1))
            .hasAttribute(AttributeKey.stringKey("input"), "second");
    }
}

Configuration and Lifecycle

Automatic Configuration

Both the JUnit 5 extension and JUnit 4 rule automatically configure:

  • SdkTracerProvider with InMemorySpanExporter
  • SdkMeterProvider with InMemoryMetricReader
  • SdkLoggerProvider with InMemoryLogRecordExporter
  • Resource with basic service information
  • Batch processors for efficient data collection

Lifecycle Management

The extensions handle the complete lifecycle:

  1. Setup (before all/each test): Initialize OpenTelemetry SDK with testing configuration
  2. Execution: Provide configured OpenTelemetry instance to tests
  3. Cleanup (after all tests): Shutdown SDK components properly
  4. Data clearing: Automatic clearing between tests (JUnit 5) or manual clearing (both)

Data Collection Behavior

// Data collection happens automatically during test execution
OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();

// Create telemetry in your code
Tracer tracer = openTelemetry.getTracer("test");
Span span = tracer.spanBuilder("operation").startSpan();
span.end();

// Data is immediately available for assertions
List<SpanData> spans = otelTesting.getSpans(); // Contains the created span

Advanced Usage Patterns

Custom Service Configuration

@Test
void testWithCustomConfiguration() {
    OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
    
    // Configure your service with additional resources or instrumentation
    MyService service = MyService.builder()
        .withOpenTelemetry(openTelemetry)
        .withResource(Resource.getDefault().merge(
            Resource.builder()
                .put(ResourceAttributes.SERVICE_NAME, "test-service")
                .put(ResourceAttributes.SERVICE_VERSION, "1.0.0")
                .build()))
        .build();
    
    service.performOperation();
    
    // Validate resource attributes
    assertThat(otelTesting.getSpans())
        .first()
        .hasResourceSatisfying(resource ->
            assertThat(resource)
                .hasAttribute(ResourceAttributes.SERVICE_NAME, "test-service")
                .hasAttribute(ResourceAttributes.SERVICE_VERSION, "1.0.0")
        );
}

Testing Error Scenarios

@Test
void testErrorHandling() {
    OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
    MyService service = new MyService(openTelemetry);
    
    // Trigger an error condition
    assertThrows(ServiceException.class, () -> 
        service.performOperation("invalid-input"));
    
    // Validate error span
    assertThat(otelTesting.getSpans())
        .first()
        .hasName("service.operation")
        .hasStatusSatisfying(status -> 
            status.hasCode(StatusCode.ERROR)
                  .hasDescription("Invalid input provided")
        )
        .hasException(ServiceException.class);
    
    // Validate error logs
    assertThat(otelTesting.getLogRecords())
        .first()
        .hasSeverity(Severity.ERROR)
        .hasBody(containsString("Failed to process input"));
}

Parameterized Tests

@ParameterizedTest
@ValueSource(strings = {"input1", "input2", "input3"})
void testParameterizedOperations(String input) {
    OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
    MyService service = new MyService(openTelemetry);
    
    service.performOperation(input);
    
    assertThat(otelTesting.getSpans())
        .hasSize(1)
        .first()
        .hasAttribute(AttributeKey.stringKey("input"), input);
    
    // Clear data for next parameterized test
    otelTesting.clearSpans();
}

Types

// JUnit 5 callback interfaces
interface BeforeEachCallback {
    void beforeEach(ExtensionContext context) throws Exception;
}

interface BeforeAllCallback {
    void beforeAll(ExtensionContext context) throws Exception;
}

interface AfterAllCallback {
    void afterAll(ExtensionContext context) throws Exception;
}

// JUnit 4 rule base class
abstract class ExternalResource implements TestRule {
    protected void before() throws Throwable;
    protected void after();
}

// OpenTelemetry SDK components configured by extensions
interface OpenTelemetry {
    TracerProvider getTracerProvider();
    MeterProvider getMeterProvider();
    LoggerProvider getLoggerProvider();
}

// Data types returned by extensions
interface SpanData { /* Completed span data */ }
interface MetricData { /* Metric measurement data */ }
interface LogRecordData { /* Log record data */ }

// Assertion helper type
class TracesAssert extends AbstractAssert<TracesAssert, Collection<List<SpanData>>> {
    TracesAssert hasTracesSatisfyingExactly(Consumer<TraceAssert>... assertions);
}

Install with Tessl CLI

npx tessl i tessl/maven-io-opentelemetry--opentelemetry-sdk-testing

docs

assertions.md

exporters.md

index.md

junit-integration.md

test-builders.md

utilities.md

tile.json