-
Notifications
You must be signed in to change notification settings - Fork 1
Integration Grpc Extension
Track gRPC client calls in your test diagrams using the TestTrackingDiagrams.Extensions.Grpc NuGet package. This extension intercepts all gRPC call types (unary, server-streaming, client-streaming, duplex-streaming) via the standard Grpc.Core.Interceptors.Interceptor API.
Using a shared library or abstraction layer? If your code doesn't use gRPC channels directly — e.g. it goes through a shared client library or custom abstraction — this extension won't be able to intercept the underlying calls. See Tracking Custom Dependencies for alternative approaches including
RequestResponseLogger.LogPair()andTrackingProxy<T>.
dotnet add package TestTrackingDiagrams.Extensions.Grpc
Your production code creates gRPC clients normally — no tracking code here:
// Program.cs / Startup.cs — register gRPC clients via DI
services.AddSingleton(sp =>
{
var channel = GrpcChannel.ForAddress("https://greeter-service:5001");
return new Greeter.GreeterClient(channel);
});
// Or using the gRPC client factory:
services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
o.Address = new Uri("https://greeter-service:5001");
});// GreetingService.cs — uses the client via DI (no tracking awareness)
public class GreetingService
{
private readonly Greeter.GreeterClient _greeterClient;
public GreetingService(Greeter.GreeterClient greeterClient)
{
_greeterClient = greeterClient;
}
public async Task<string> GetGreeting(string name)
{
var reply = await _greeterClient.SayHelloAsync(new HelloRequest { Name = name });
return reply.Message;
}
}In your test setup, replace the production gRPC client registration with a tracked version. The WithTestTracking extension wraps the channel with a GrpcTrackingInterceptor that records all gRPC calls for sequence diagrams:
// TestSetupHooks.cs or WebApplicationFactory setup
builder.ConfigureTestServices(services =>
{
// Replace the production client with a tracked one
services.AddSingleton(sp =>
{
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.WithTestTracking(new GrpcTrackingOptions
{
ServiceName = "GreeterService",
CallingServiceName = "MyApi",
Verbosity = GrpcTrackingVerbosity.Detailed,
CurrentTestInfoFetcher = () => (TestContext.CurrentTestName, TestContext.CurrentTestId)
});
return new Greeter.GreeterClient(invoker);
});
});Or use the interceptor directly if you prefer:
var interceptor = new GrpcTrackingInterceptor(new GrpcTrackingOptions
{
ServiceName = "GreeterService",
CallingServiceName = "MyApi",
CurrentTestInfoFetcher = () => (TestContext.CurrentTestName, TestContext.CurrentTestId)
});
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.Intercept(interceptor);
var client = new Greeter.GreeterClient(invoker);Key point: Your production code (controllers, services, etc.) stays unchanged. The only difference is how the gRPC client is constructed — tests wrap the channel with the tracking interceptor via DI replacement.
If your SUT exposes a gRPC service (i.e. the test calls the SUT via gRPC), you can get rich gRPC-aware diagrams for the incoming leg too — with protobuf message deserialization, operation classification, and grpc:// URIs. Without this, incoming gRPC calls would appear as raw HTTP/2 POST requests with binary bodies.
The simplest approach mirrors the HTTP convenience API. This single call handles everything — ResponseVersionHandler (HTTP/2 fix), channel creation, tracking interceptor, and client construction:
using TestTrackingDiagrams.Extensions.Grpc;
var client = factory.CreateTestTrackingGrpcClient<Program, Greeter.GreeterClient>(
new GrpcTrackingOptions
{
ServiceName = "My API",
CallingServiceName = "Caller",
CurrentTestInfoFetcher = () => (testName, testId)
});If you need to dispose the underlying GrpcChannel cleanly (e.g. in [AfterScenario]), use the WithChannel variant:
var (client, channel) = factory.CreateTestTrackingGrpcClientWithChannel<Program, Greeter.GreeterClient>(
new GrpcTrackingOptions
{
ServiceName = "My API",
CallingServiceName = "Caller",
CurrentTestInfoFetcher = () => (testName, testId)
});
// Later, in cleanup:
channel.Dispose();For more control (e.g. when you need a custom handler chain), use GrpcTrackingChannel.Create() directly. When testing against TestServer, wrap the handler with GrpcResponseVersionHandler to fix the HTTP/1.1 → HTTP/2 version mismatch:
using TestTrackingDiagrams.Extensions.Grpc;
// In your test setup (e.g. constructor, [BeforeScenario], etc.)
var invoker = GrpcTrackingChannel.Create(
new GrpcResponseVersionHandler(factory.Server.CreateHandler()),
factory.Server.BaseAddress,
new GrpcTrackingOptions
{
ServiceName = "My API",
CallingServiceName = "Caller",
Verbosity = GrpcTrackingVerbosity.Detailed,
CurrentTestInfoFetcher = () =>
{
var test = TestContext.Current.Test;
return test is not null
? (test.TestDisplayName, test.UniqueID)
: ("Unknown", "unknown");
}
});
// Create your typed gRPC client from the tracked invoker
var client = new Greeter.GreeterClient(invoker);If you need to dispose the underlying GrpcChannel cleanly (e.g. in [AfterScenario]), use CreateWithChannel:
var (invoker, channel) = GrpcTrackingChannel.CreateWithChannel(
new GrpcResponseVersionHandler(factory.Server.CreateHandler()),
factory.Server.BaseAddress,
new GrpcTrackingOptions
{
ServiceName = "My API",
CallingServiceName = "Caller",
CurrentTestInfoFetcher = () => (TestContext.Current.Test!.TestDisplayName, TestContext.Current.Test!.UniqueID)
});
var client = new Greeter.GreeterClient(invoker);
// Later, in cleanup:
channel.Dispose();There's also an extension method on HttpMessageHandler for terser syntax:
var invoker = factory.Server.CreateHandler()
.AsGrpcTrackingCallInvoker(factory.Server.BaseAddress, grpcOptions);| Aspect | Without gRPC extension | With GrpcTrackingChannel.Create()
|
|---|---|---|
| Label | POST |
SayHello or StreamOrders (server-stream)
|
| URI | http://localhost/greet.Greeter/SayHello |
grpc:///greet.Greeter/SayHello |
| Request body | Binary protobuf (unreadable) | JSON-formatted protobuf fields |
| Response body | Binary protobuf | JSON-formatted protobuf fields |
| Operation type | None |
UnaryCall, ServerStreamingCall, etc. |
| Status mapping | HTTP/2 status | gRPC StatusCode → HTTP mapping |
| Dependency colour | HTTP (blue) | gRPC (uses DependencyCategory: "gRPC") |
Important: Do not also wrap the underlying HTTP handler with
TestTrackingMessageHandlerorCreateTestTrackingClient()for gRPC calls — this would produce duplicate diagram entries (one from the HTTP layer, one from the gRPC interceptor). UseGrpcTrackingChannel.Create()instead of, not in addition to, HTTP tracking for the gRPC test client.
Tip: If your test project makes both HTTP and gRPC calls to the SUT, use
CreateTestTrackingClient()for the HTTP client andGrpcTrackingChannel.Create()for the gRPC client. They log to the sameRequestResponseLoggerand will appear together in the same diagram.
GrpcTrackingInterceptor extends Grpc.Core.Interceptors.Interceptor, overriding all five call types:
| Call Type | Override | Tracking |
|---|---|---|
| Async Unary | AsyncUnaryCall |
Full request + response |
| Blocking Unary | BlockingUnaryCall |
Full request + response |
| Server Streaming | AsyncServerStreamingCall |
Request + call initiation |
| Client Streaming | AsyncClientStreamingCall |
Call initiation + response |
| Duplex Streaming | AsyncDuplexStreamingCall |
Call initiation + response |
Streaming calls are tracked at the initiation level (not per-message) to keep diagrams clean.
| Method Type | Enum Value |
|---|---|
| Unary | GrpcOperation.UnaryCall |
| Server Streaming | GrpcOperation.ServerStreamingCall |
| Client Streaming | GrpcOperation.ClientStreamingCall |
| Duplex Streaming | GrpcOperation.DuplexStreamingCall |
Request and response messages are serialized for diagram content:
-
Protobuf
IMessage: UsesGoogle.Protobuf.JsonFormatter.Default.Format()for JSON output -
Other types: Falls back to
.ToString() - Summarised verbosity: Content omitted entirely
gRPC status codes are mapped to HTTP status codes for consistent diagram logging:
| gRPC Status | HTTP Status |
|---|---|
| OK | 200 OK |
| NotFound | 404 Not Found |
| PermissionDenied | 403 Forbidden |
| Unauthenticated | 401 Unauthorized |
| InvalidArgument | 400 Bad Request |
| DeadlineExceeded | 408 Request Timeout |
| AlreadyExists | 409 Conflict |
| ResourceExhausted | 429 Too Many Requests |
| Unavailable | 503 Service Unavailable |
| Unimplemented | 501 Not Implemented |
| Cancelled | 408 Request Timeout |
| Other | 500 Internal Server Error |
Minimal output:
-
Label: Method name only (e.g.
SayHello) -
URI:
grpc:///ServiceName/ - Content: Omitted
- Headers: Omitted
Balanced output:
-
Label: Method name with streaming annotation (e.g.
SayHello,StreamMessages (server-stream)) -
URI:
grpc:///ServiceName/MethodName - Content: Serialized protobuf messages
- Headers: Omitted
Full output:
-
Label:
/package.Service/Method [UnaryCall] -
URI:
grpc:///package.Service/Method - Content: Serialized protobuf messages
- Headers: Included (non-binary metadata entries)
new GrpcTrackingOptions
{
// Display name in diagrams (default: "GrpcService")
ServiceName = "GreeterService",
// Calling service name in diagrams (default: "Caller")
CallingServiceName = "MyApi",
// Verbosity level (default: Detailed)
Verbosity = GrpcTrackingVerbosity.Detailed,
// Required: provides test context
CurrentTestInfoFetcher = () => (testName, testId),
// Use proto service name instead of ServiceName (default: false)
UseProtoServiceNameInDiagram = false
}| Property | Type | Default | Description |
|---|---|---|---|
ServiceName |
string |
"GrpcService" |
Display name in diagrams for the gRPC service |
CallingServiceName |
string |
"Caller" |
Calling service name in diagrams |
Verbosity |
GrpcTrackingVerbosity |
Detailed |
Verbosity level (Raw, Detailed, Summarised) |
CurrentTestInfoFetcher |
Func<(string Name, string Id)>? |
null |
Required: provides test context |
CurrentStepTypeFetcher |
Func<string?>? |
null |
Optional — returns the current BDD step type (Given/When/Then) |
v2.23.0+ Dual-Resolution:
GrpcTrackingInterceptoraccepts an optionalIHttpContextAccessor? httpContextAccessorconstructor parameter for resolving test identity from HTTP request headers when running inside the SUT's request pipeline. See HTTP Tracking Setup#Dual-Resolution Test Identity (v2.23.0+) for details.
| UseProtoServiceNameInDiagram | bool | false | Use proto service name instead of ServiceName |
| SetupVerbosity | GrpcTrackingVerbosity? | null | Verbosity override for the Setup phase. See Phase-Aware Tracking |
| ActionVerbosity | GrpcTrackingVerbosity? | null | Verbosity override for the Action phase. See Phase-Aware Tracking |
| TrackDuringSetup | bool | true | When false, tracking is suppressed during Setup. See Phase-Aware Tracking |
| TrackDuringAction | bool | true | When false, tracking is suppressed during Action. See Phase-Aware Tracking |
When true, the proto service name from the method definition (e.g. greet.Greeter) is used as the service name in diagrams instead of the configured ServiceName. Useful when tracking multiple gRPC services through a single interceptor.
GrpcTrackingInterceptor implements ITrackingComponent and auto-registers with TrackingComponentRegistry:
-
ComponentName:"GrpcTrackingInterceptor ({ServiceName})" -
WasInvoked:trueafter first call -
InvocationCount: Total calls processed (all call types)
| Verbosity | Format | Example |
|---|---|---|
| Summarised | grpc:///ServiceName/ |
grpc:///greet.Greeter/ |
| Detailed | grpc:///ServiceName/MethodName |
grpc:///greet.Greeter/SayHello |
| Raw | grpc:///FullMethodPath |
grpc:////greet.Greeter/SayHello |
When testing gRPC services in-process via TestServer (e.g. from WebApplicationFactory), TestServer returns HTTP/1.1 responses, but gRPC requires HTTP/2. Without fixing this, the gRPC client throws RpcException with status Internal.
GrpcResponseVersionHandler is a DelegatingHandler that copies the request version onto the response, resolving the mismatch:
// Wrap TestServer's handler for gRPC compatibility
var handler = new GrpcResponseVersionHandler(factory.Server.CreateHandler());Note: You do not need to use
GrpcResponseVersionHandlerdirectly if you useCreateTestTrackingGrpcClient— it is applied automatically.
As of v2.23.8, gRPC calls tracked by GrpcTrackingInterceptor automatically appear in Activity Diagrams and Flamecharts. The interceptor:
- Creates a
System.Diagnostics.Activityspan (viaActivitySource("TestTrackingDiagrams.Grpc")) around each gRPC call - Populates
ActivityTraceId,ActivitySpanId, andTimestampon all logged request/response entries - Lazily starts the
InternalFlowActivityListenerto capture spans into the span store
No additional configuration is required — if your report includes Activity Diagrams or Flamecharts, gRPC calls will appear alongside HTTP calls automatically.
- HTTP Tracking Setup — core HTTP tracking (for non-gRPC dependencies)
- Tracking Dependencies — faking dependencies and routing HTTP through the tracking pipeline
- Tracking Custom Dependencies — manual logging for dependencies that bypass HTTP entirely
- Phase-Aware Tracking — configuring different verbosity/enable per test phase
-
Event & Message Tracking — when to use
MessageTrackervsGrpcTrackingInterceptor - Integration DispatchProxy Extension — alternative proxy-based tracking for interface-based gRPC wrappers
Getting Started
Common Tasks
Integration Guides
- Integration xUnit3
- Integration xUnit2
- Integration NUnit
- Integration MSTest
- Integration TUnit
- Integration BDDfy xUnit3
- Integration LightBDD xUnit2
- Integration LightBDD xUnit3
- Integration LightBDD TUnit
- Integration ReqNRoll xUnit2
- Integration ReqNRoll xUnit3
- Integration ReqNRoll TUnit
Extensions
- Integration AtlasDataApi Extension
- Integration BigQuery Extension
- Integration Bigtable Extension
- Integration BlobStorage Extension
- Integration ClickHouse Extension
- Integration CloudStorage Extension
- Integration CosmosDB Extension
- Integration Dapper Extension
- Integration DynamoDB Extension
- Integration EF Core Relational Extension
- Integration Elasticsearch Extension
- Integration EventBridge Extension
- Integration EventHubs Extension
- Integration Grpc Extension
- Integration Kafka Extension
- Integration MassTransit Extension
- Integration MongoDB Extension
- Integration MySqlConnector Extension
- Integration Npgsql Extension
- Integration Oracle Extension
- Integration PubSub Extension
- Integration Redis Extension
- Integration S3 Extension
- Integration ServiceBus Extension
- Integration SNS Extension
- Integration Spanner Extension
- Integration SqlClient Extension
- Integration Sqlite Extension
- Integration SQS Extension
- Integration StorageQueues Extension
- Integration OpenTelemetry Extension
- Integration DispatchProxy Extension
- Integration MediatR Extension
- Integration PlantUML IKVM
Configuration
- Tracking Dependencies
- Tracking Custom Dependencies
- HTTP Tracking Setup
- Report Configuration
- Diagram Customisation
- Phase-Aware Tracking
- Content Formatting
- PlantUML Server Configuration
Features
- Generated Reports
- Search Syntax
- Component Diagrams
- PlantUML Browser Rendering
- Inline SVG Rendering
- Internal Flow Tracking
- Tags and Attributes
- Excluding Requests
- Excluded Headers
- Multi-Host Test Architectures
- Event-Driven Architecture Testing
- Service Bus Tracking Patterns
- Background Thread Correlation
- Parallel-Safe Background Correlation
- Event & Message Tracking
- Assertion Tracking
- Step Tracking
- Tabular Attributes
- Large Response and Diagram Handling
- Diagnostics and Debugging
- CI Summary Integration
- CI Artifact Upload
- Merging Parallel Reports
Reference