open-telemetry / test-utils
Testing utilities for OpenTelemetry PHP instrumentations
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3
- open-telemetry/api: ^1.0
- open-telemetry/sdk: ^1.0
- phan/phan: ^5.0
- phpstan/phpstan: ^1.1
- phpstan/phpstan-phpunit: ^1.0
- phpunit/phpunit: ^9.5
- psalm/plugin-phpunit: ^0.19.2
- vimeo/psalm: ^4|^5|^6
README
This is a read-only subtree split of https://github.com/open-telemetry/opentelemetry-php-contrib.
OpenTelemetry Test Utilities
This package provides testing utilities for OpenTelemetry PHP instrumentations. It includes tools to help test and validate trace structures, span relationships, and other OpenTelemetry-specific functionality.
Features
TraceStructureAssertionTrait
The TraceStructureAssertionTrait
provides methods to assess if spans match an expected trace structure. It's particularly useful for testing complex trace hierarchies and relationships between spans.
Key features:
- Support for hierarchical span relationships
- Verification of span names, kinds, attributes, events, and status
- Flexible matching with strict and non-strict modes
- Support for PHPUnit matchers/constraints for more flexible assertions
- Detailed error messages for failed assertions
- Two interfaces: array-based and fluent
Requirements
- PHP 7.4 or higher
- OpenTelemetry SDK and API (for testing)
- PHPUnit 9.5 or higher
Usage
TraceStructureAssertionTrait
Add the trait to your test class:
use OpenTelemetry\TestUtils\TraceStructureAssertionTrait; use PHPUnit\Framework\TestCase; class MyTest extends TestCase { use TraceStructureAssertionTrait; // Your test methods... }
Array-Based Interface
Use the assertTraceStructure
method to verify trace structures using an array-based approach:
public function testTraceStructure(): void { // Create spans using the OpenTelemetry SDK // ... // Define the expected structure $expectedStructure = [ [ 'name' => 'root-span', 'kind' => SpanKind::KIND_SERVER, 'children' => [ [ 'name' => 'child-span', 'kind' => SpanKind::KIND_INTERNAL, 'attributes' => [ 'attribute.one' => 'value1', 'attribute.two' => 42, ], 'events' => [ [ 'name' => 'event.processed', 'attributes' => [ 'processed.id' => 'abc123', ], ], ], ], [ 'name' => 'another-child-span', 'kind' => SpanKind::KIND_CLIENT, 'status' => [ 'code' => StatusCode::STATUS_ERROR, 'description' => 'Something went wrong', ], ], ], ], ]; // Assert the trace structure $this->assertTraceStructure($spans, $expectedStructure); }
The assertTraceStructure
method takes the following parameters:
$spans
: An array or ArrayObject of spans (typically from an InMemoryExporter)$expectedStructure
: An array defining the expected structure of the trace$strict
(optional): Whether to perform strict matching (all attributes must match)
Fluent Interface
Use the assertTrace
method to verify trace structures using a fluent, chainable interface:
public function testTraceStructure(): void { // Create spans using the OpenTelemetry SDK // ... // Assert the trace structure using the fluent interface $this->assertTrace($spans) ->hasRootSpan('root-span') ->withKind(SpanKind::KIND_SERVER) ->hasChild('child-span') ->withKind(SpanKind::KIND_INTERNAL) ->withAttribute('attribute.one', 'value1') ->withAttribute('attribute.two', 42) ->hasEvent('event.processed') ->withAttribute('processed.id', 'abc123') ->end() ->end() ->hasChild('another-child-span') ->withKind(SpanKind::KIND_CLIENT) ->withStatus(StatusCode::STATUS_ERROR, 'Something went wrong') ->end() ->end(); }
The fluent interface provides the following methods:
TraceAssertion:
hasRootSpan(string|Constraint $name)
: Assert that the trace has a root span with the given namehasRootSpans(int $count)
: Assert that the trace has the expected number of root spansinStrictMode()
: Enable strict mode for all assertions
SpanAssertion:
withKind(int|Constraint $kind)
: Assert that the span has the expected kindwithAttribute(string $key, mixed|Constraint $value)
: Assert that the span has an attribute with the expected key and valuewithAttributes(array $attributes)
: Assert that the span has the expected attributeswithStatus(int|Constraint $code, string|Constraint|null $description = null)
: Assert that the span has the expected statushasEvent(string|Constraint $name)
: Assert that the span has an event with the expected namehasChild(string|Constraint $name)
: Assert that the span has a child span with the expected namehasChildren(int $count)
: Assert that the span has the expected number of childrenend()
: Return to the parent assertion
SpanEventAssertion:
withAttribute(string $key, mixed|Constraint $value)
: Assert that the event has an attribute with the expected key and valuewithAttributes(array $attributes)
: Assert that the event has the expected attributesend()
: Return to the parent span assertion
Using PHPUnit Matchers
You can use PHPUnit constraints/matchers for more flexible assertions with both interfaces:
Array-Based Interface with Matchers
use PHPUnit\Framework\Constraint\Callback; use PHPUnit\Framework\Constraint\IsIdentical; use PHPUnit\Framework\Constraint\IsType; use PHPUnit\Framework\Constraint\RegularExpression; use PHPUnit\Framework\Constraint\StringContains; // Define the expected structure with matchers $expectedStructure = [ [ 'name' => 'root-span', 'kind' => new IsIdentical(SpanKind::KIND_SERVER), 'attributes' => [ 'string.attribute' => new StringContains('World'), 'numeric.attribute' => new Callback(function ($value) { return $value > 40 || $value === 42; }), 'boolean.attribute' => new IsType('boolean'), 'array.attribute' => new Callback(function ($value) { return is_array($value) && count($value) === 3 && in_array('b', $value); }), ], 'children' => [ [ 'name' => new RegularExpression('/child-span-\d+/'), 'kind' => SpanKind::KIND_INTERNAL, 'attributes' => [ 'timestamp' => new IsType('integer'), ], 'events' => [ [ 'name' => 'process.start', 'attributes' => [ 'process.id' => new IsType('integer'), 'process.name' => new StringContains('process'), ], ], ], ], ], ], ]; // Assert the trace structure with matchers $this->assertTraceStructure($spans, $expectedStructure);
Fluent Interface with Matchers
use PHPUnit\Framework\Constraint\Callback; use PHPUnit\Framework\Constraint\IsIdentical; use PHPUnit\Framework\Constraint\IsType; use PHPUnit\Framework\Constraint\RegularExpression; use PHPUnit\Framework\Constraint\StringContains; // Assert the trace structure using the fluent interface with matchers $this->assertTrace($spans) ->hasRootSpan('root-span') ->withKind(new IsIdentical(SpanKind::KIND_SERVER)) ->withAttribute('string.attribute', new StringContains('World')) ->withAttribute('numeric.attribute', new Callback(function ($value) { return $value > 40 || $value === 42; })) ->withAttribute('boolean.attribute', new IsType('boolean')) ->withAttribute('array.attribute', new Callback(function ($value) { return is_array($value) && count($value) === 3 && in_array('b', $value); })) ->hasChild(new RegularExpression('/child-span-\d+/')) ->withKind(SpanKind::KIND_INTERNAL) ->withAttribute('timestamp', new IsType('integer')) ->hasEvent('process.start') ->withAttribute('process.id', new IsType('integer')) ->withAttribute('process.name', new StringContains('process')) ->end() ->end() ->end();
Supported PHPUnit matchers include:
StringContains
for partial string matchingRegularExpression
for pattern matchingIsIdentical
for strict equalityIsEqual
for loose equalityIsType
for type checkingCallback
for custom validation logic
Installation via composer
$ composer require --dev open-telemetry/test-utils
Installing dependencies and executing tests
From the Test Utils subdirectory:
$ composer install $ ./vendor/bin/phpunit tests