In ASP.NET Core unit tests, if you want to mock HttpContext.Features.Get<SomeType>(), here's the trick.

Problem


I have my Error page code that will get exception detail infomation, to do that, I use HttpContext.Features.Get<IExceptionHandlerPathFeature>().

public void OnGet()
{
    var requestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
    var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
    if (exceptionFeature is not null)
    {
        // Get which route the exception occurred at
        var routeWhereExceptionOccurred = exceptionFeature.Path;

        // Get the exception that occurred
        var exceptionThatOccurred = exceptionFeature.Error;
        _logger.LogError($"Error: {routeWhereExceptionOccurred}, " +
                         $"client IP: {HttpContext.Connection.RemoteIpAddress}, " +
                         $"request id: {requestId}", exceptionThatOccurred);
    }

    RequestId = requestId;
}

Now I want to unit test this piece of code. Usually I will use DefaultHttpContext instance in unit tests that requires an HttpContext for a page or controller. But I found the Features property is readonly on the HttpContext class. So there's no way to assign values that we mocked to this property.

namespace Microsoft.AspNetCore.Http
{
    public abstract class HttpContext
    {
        protected HttpContext();

        //
        // Summary:
        //     Gets the collection of HTTP features provided by the server and middleware available
        //     on this request.
        public abstract IFeatureCollection Features { get; }
		
        //  ...
    }
}

Solution


First, prepare the mock as usual. In my case, I set up IFeatureCollection.Get() method to return my desired object.

var mockIFeatureCollection = _mockRepository.Create<IFeatureCollection>();
mockIFeatureCollection.Setup(p => p.Get<IExceptionHandlerPathFeature>())
    .Returns(new ExceptionHandlerFeature
    {
        Path = "/996/icu",
        Error = new("Too much fubao")
    });
httpContextMock.Setup(p => p.Features).Returns(mockIFeatureCollection.Object);

Then, in order to assign values to HttpContext.Features, we can't use DefaultHttpContext this time. We have to create a mock for HttpContext. And set up the Features property to return our mocked IFeatureCollection object.

var httpContextMock = _mockRepository.Create<HttpContext>();
httpContextMock.Setup(p => p.Features).Returns(mockIFeatureCollection.Object);

Now, run the unit test, we can see values are coming up correctly.