Search code examples
unit-testing.net-coremiddlewarehttpcontextexceptionhandler

Initializing DefaultHttpContext.Response.Body to MemoryStream throws NullReferencePointer


I am working on implementation of global exception middleware and I would like to cover middleware with unit tests. Below you see how far I got.

This is a code of unit test.

    [Fact]
    public async Task MethodName_StateUnderTest_ExpectedBehavior()
    {
        //Arrange
        IExceptionHandlerFeature exceptionHandlerFeature = new ExceptionHandlerFeature {Error = new NotFoundException()};

        IFeatureCollection features = new FeatureCollection();
        features.Set(exceptionHandlerFeature);

        var context = new DefaultHttpContext(features);
        context.Response.Body = new MemoryStream();
        //Act
        await ExceptionMiddleware.HandleException(context);

        //Assert
        context.Response.StatusCode.Should().Be((int) HttpStatusCode.NotFound);
    }

This is a code of ExceptionMiddleware.Handle method

public static async Task HandleException(HttpContext context)
{
    var contextFeature = context.Features.Get<IExceptionHandlerFeature>();

    if (contextFeature == null)
    {
        return;
    }

    if (contextFeature.Error is AppValidationException validationException)
    {
        context.Response.StatusCode = (int) HttpStatusCode.BadRequest;

        var failures = JsonConvert.SerializeObject(validationException.Failures);

        await context.Response.WriteAsync(
            new ErrorDetails
            {
                StatusCode = context.Response.StatusCode,
                Message = contextFeature.Error.Message,
                StackTrace = contextFeature.Error.StackTrace,
                Detail = failures
            }.ToString());

        return;
    }

    context.Response.StatusCode = (int) ResolveStatusCodeFromExceptionType(contextFeature.Error);
    context.Response.ContentType = "application/json";

    await context.Response.WriteAsync(
        new ErrorDetails
        {
            StatusCode = context.Response.StatusCode,
            Message = contextFeature.Error.Message,
            StackTrace = contextFeature.Error.StackTrace
        }.ToString());
}

Test is crushing on line

context.Response.Body = new MemoryStream();

According to this question, everything should be fine but I still can't initialize property Body.

Test Projects target framework is .Net Core 3.0. ExceptionMiddleware.cs is in project with set target framework .NET Standard 2.1 and it is Class Library.

StackTrace of exception is really short:

at Microsoft.AspNetCore.Http.Internal.DefaultHttpResponse.set_Body(Stream value) at Itixis.Shared.API.Tests.ExceptionMiddlewareTests.d__0.MoveNext() in C:\Users\daniel.rusnok\source\repos\Itixis\Itixis.Shared\Tests\Itixis.Shared.API.Tests\ExceptionMiddlewareTests.cs:line 35


Solution

  • By manually setting the features you are removing the ones set by default, which causes your null error.

        public DefaultHttpContext()
            : this(new FeatureCollection())
        {
            Features.Set<IHttpRequestFeature>(new HttpRequestFeature());
            Features.Set<IHttpResponseFeature>(new HttpResponseFeature());
            Features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
        }
    
        public DefaultHttpContext(IFeatureCollection features)
        {
            _features.Initalize(features);
            _request = new DefaultHttpRequest(this);
            _response = new DefaultHttpResponse(this);
        }
    

    Source

    Note the request, response and response body features added in the default constructor.

    Either try recreating what was done in the default constructor by also adding the required features,

    IExceptionHandlerFeature exceptionHandlerFeature = new ExceptionHandlerFeature {Error = new NotFoundException()};
    
    IFeatureCollection features = new FeatureCollection();
    features.Set(exceptionHandlerFeature);
    features.Set<IHttpRequestFeature>(new HttpRequestFeature());
    features.Set<IHttpResponseFeature>(new HttpResponseFeature());
    features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
    
    var context = new DefaultHttpContext(features);
    
    //...
    

    or remove the manual feature and use the default constructor.

    [Fact]
    public async Task MethodName_StateUnderTest_ExpectedBehavior() {
        //Arrange
        var context = new DefaultHttpContext();
        context.Response.Body = new MemoryStream();
        //Act
        await ExceptionMiddleware.HandleException(context);
    
        //Assert
        context.Response.StatusCode.Should().Be((int) HttpStatusCode.NotFound);
    }
    

    and it should work as expected.