Search code examples
asp.net-coreintegration-testing

Using IHttpActivityFeature in ASP.NET Core integration tests


I am writing a piece of middleware for ASP.NET Core 8 that makes use of the IHttpActivityFeature; in my integration tests, I am using WebHostBuilder to create an isolated test server for my middleware to run on.

However, WebHostBuilder is not configuring the IHttpActivityFeature out of the box, so when I request the feature from the HttpContext, I get a null reference; so far I have been unable to track down any information on how this feature is enabled, or how to add it to my test server.

My middleware is:

public class TraceIdMiddleware
{
    private const string TraceIdHeader = "X-Trace-Id";
    private readonly RequestDelegate _next;

    public TraceIdMiddleware(RequestDelegate next)
    {
        _next = next;
    }
        
    public Task Invoke(HttpContext context)
    {
        context.Response.OnStarting(() => {

            var activityFeature = context.Features.Get<IHttpActivityFeature>();
            var traceId = activityFeature.Activity.TraceId.ToString();
            context.Response.Headers.TryAdd(TraceIdHeader, traceId);
            return Task.CompletedTask;
        });
            
        return _next(context);
    }
}

And it is tested by this code:

public class WhenMakingAnHttpRequest
{
    [Fact]
    public async Task ItShouldAddTheTraceIdHeader()
    {
        var builder = new WebHostBuilder()
            .Configure(applicationBuilder => {
                applicationBuilder
                    .UseTraceId()
                    .Map("/trace-id", app => {
                        app.Run(async context => {
                            await context.Response.WriteAsync("Testing TraceId Middleware");
                        });
                    });
            });

        using var server = new TestServer(builder);
        using var client = server.CreateClient();

        var response = await client.GetAsync("/trace-id");
        response.Headers
            .Should().ContainKey("X-Trace-Id").WhoseValue
            .Should().NotBeNullOrEmpty();
    }
}

The test fails when the middleware throws a NullReferenceException due to the IHttpActivityFeature not being registered with the HttpContext when I call Features.Get<T>() on the context.

How can I configure the test server to return an IHttpActivityFeature?


Solution

  • Modify your code like below:

    WhenMakingAnHttpRequest

    public class WhenMakingAnHttpRequest
    {
         [Fact]
         public async Task ItShouldAddTheTraceIdHeader()
         {
             var builder = new WebHostBuilder()
             .ConfigureServices(services =>
             {
                 // Configure IHttpActivityFeature
                 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
                 services.AddSingleton<IHttpActivityFeature>(provider =>
                 {
                     var httpContext = provider.GetRequiredService<IHttpContextAccessor>().HttpContext;
                     var activity = new Activity("HTTP Activity");
                     activity.Start();
                     return new MockHttpActivityFeature(activity);
                 });
             })
             .Configure(applicationBuilder =>
             {
                 applicationBuilder
                     .UseMiddleware<TraceIdMiddleware>()
                     .Map("/trace-id", app =>
                     {
                         app.Run(async context =>
                         {
                             await context.Response.WriteAsync("Testing TraceId Middleware");
                         });
                     });
             });
    
             using var server = new TestServer(builder);
             using var client = server.CreateClient();
    
             // Act
             var response = await client.GetAsync("/trace-id");
    
             // Assert
             Assert.True(response.Headers.Contains("X-Trace-Id"));
    
         }
     }
    

    Custom implementation of ·IHttpActivityFeature·

     public class MockHttpActivityFeature : IHttpActivityFeature
     {
         public Activity Activity { get; set; }
         public MockHttpActivityFeature(Activity activity)
         {
             Activity = activity;
    
         }
     }
    

    Middleware

    public class TraceIdMiddleware
    {
        private const string TraceIdHeader = "X-Trace-Id";
        private readonly RequestDelegate _next;
    
        public TraceIdMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public async Task Invoke(HttpContext context, IHttpActivityFeature activityFeature)
        {
            if (activityFeature?.Activity != null)
            {
                var traceId = activityFeature.Activity.TraceId.ToString();
                context.Response.Headers.TryAdd(TraceIdHeader, traceId);
            }
    
            await _next(context);
        }      
    }