Mocking HttpContext in ASPNET Core

Mocking HttpContext in ASPNET Core

How the new dotnet core implementation makes testing easier

If you have been developing web apps with C# and the .NET framework for any length of time, chances are, you are familiar with the following code snippet.

var userName = HttpContext.Current.User.Identity.Name;

The code is simple. We've received a web request and we want the name of the currently logged in user for whatever our web app needs it for, and this property will give us that in a very easy and convenient way. It may be a good idea to just write our code like this and move on with our lives, but if you're a developer who is constantly worrying about code quality, reliability, maintainability and having good unit test coverage, then this is a "trap" for sure. trap.png The problem with the HttpContext object in .NET framework is that it is very difficult to mock. Introducing this in a method almost renders our code untestable. This article from over 10 years ago explains this point very well and even gives recommendations on how to use it, while keeping the code base testable. However, using static methods that mutate some sort of global state make it very hard to write code that can be easily isolated and tested as a unit.

Dotnet Core to the rescue

The dotnet team were well aware of these issues and with dotnet core, it is now very easy to mock HttpContext. It is now no longer this giant global object. The HttpContext is now accessible as a property on the ControllerBase and PageModel classes. So you can easily access it in your controllers in your ASP.NET MVC app or inside your Razor pages. Let's take a look at how this property is defined inside these classes.

// ControllerBase.cs - MVC apps
/// <summary>
/// Gets the <see cref="Http.HttpContext"/> for the executing action.
/// </summary>
public HttpContext HttpContext => ControllerContext.HttpContext;

// PageModel.cs - Razor Pages
/// <summary>
/// Gets the <see cref="Http.HttpContext"/>.
/// </summary>
public HttpContext HttpContext => PageContext.HttpContext;

As you can see, the implementation is exactly the same for both except depending on whether you're using MVC or Razor pages, it is being set by the ControllerContext or the PageContext. But this will still not allow us to easily mock this, as the property has no setter and it isn't marked "virtual" which allows mocking frameworks to easily create mocks. So let's take a look at how the ControllerContext and PageContext properties are implemented.

        /// <summary>
        /// Gets or sets the <see cref="Mvc.ControllerContext"/>.
        /// </summary>
        /// <remarks>
        /// <see cref="Controllers.IControllerActivator"/> activates this property while activating controllers.
        /// If user code directly instantiates a controller, the getter returns an empty
        /// <see cref="Mvc.ControllerContext"/>.
        /// </remarks>
        [ControllerContext]
        public ControllerContext ControllerContext
        {
            get
            {
                if (_controllerContext == null)
                {
                    _controllerContext = new ControllerContext();
                }

                return _controllerContext;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }

                _controllerContext = value;
            }
        }

        /// <summary>
        /// Gets the <see cref="RazorPages.PageContext"/>.
        /// </summary>
        [PageContext]
        public PageContext PageContext
        {
            get
            {
                if (_pageContext == null)
                {
                    _pageContext = new PageContext();
                }

                return _pageContext;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }

                _pageContext = value;
            }
        }

Based on this code, we should be able to easily instantiate these classes. Both ControllerContext and PageContext classes inherit from the ActionContext class which exposes an HttpContext property. It is this one that we can use to mock in our tests. So let's take the example earlier where we're accessing a user's name in one of our methods which we will be testing. The following is the method under test.

        public string UserName()
        {
            var userName = HttpContext?.User.Identity?.Name;
            return userName;
        }

The example is a bit contrived but will help us demonstrate how to easily mock HttpContext. The mocking framework I'm using is NSubstitute but this can be easily achieved with any other mocking framework. The following code is a unit test written for the UserName method.

        [Fact]
        public void UserName_WhenInvokedAndValidContext_ReturnsUserName()
        {
            // Arrange
            // Based on the code above we are able to instantiate the
            // the PageContext and its HttpContext property.
            var privacyModel = new PrivacyModel(_logger)
            {
                PageContext = new PageContext
                {
                    HttpContext = new DefaultHttpContext()
                }
            };
            // We create a stub for the user, so we can tell NSubstitute what
            // to return
            var user = Substitute.For<ClaimsPrincipal>();
            privacyModel.PageContext.HttpContext.User = user;
            // Create stub for the Identity property of the User
            var identity = Substitute.For<IIdentity>();
            privacyModel.HttpContext.User.Identity.Returns(identity);
            const string expectedUserName = "John Doe";
            // Finally, setting the Name property to return our
            // expected user name for this test
            privacyModel.HttpContext.User.Identity?.Name.Returns(expectedUserName);

            // Act
            var actual = privacyModel.UserName();

            // Assert
            actual.Should().NotBeNullOrEmpty();
            actual.Should().BeOfType<string>();
            actual.Should().Be(expectedUserName);
        }

Based on this approach we can easily test methods that access HttpContext in dotnet core. Let's take another example of an ASP.NET Web Api action method where we access HttpContext to get an exception and then log it somewhere.

        [Route("/error")]
        public IActionResult Error()
        {
            var exceptionHandlerFeature = HttpContext.Features.Get<IExceptionHandlerFeature>();
            _logger.LogError($"{exceptionHandlerFeature.Error.Message} : {exceptionHandlerFeature.Error.StackTrace}");

            return Problem();
        }

The code is pretty simple. It uses the IExceptionHandlerFeature to get access to the exception that has occurred. Following is the unit test for this method.

        [Fact]
        public void Error_WhenErrorInvoked_LogErrorToConsole()
        {
            // Arrange
            // Same as the previous test, instead of the PageContext,
            // we instantiate the ControllerContext
            var logger = Substitute.For<ILogger<ErrorController>>();
            var controller = new ErrorController(logger)
            {
                ControllerContext = new ControllerContext
                {
                    HttpContext = new DefaultHttpContext()
                }
            };
            // Setting up our exceptionHandlerfeature variable to return the
            // desired exception.
            var exceptionHandlerFeature = Substitute.For<IExceptionHandlerFeature>();
            var exception = Substitute.For<Exception>("There was an error.");
            exception.StackTrace.Returns("Sample Error stack trace.");
            exceptionHandlerFeature.Error.Returns(exception);
            controller.HttpContext.Features.Set(exceptionHandlerFeature);

            // Act
            var response = controller.Error();

            // Assert
            response.Should().NotBeNull();
            response.Should().BeAssignableTo<IActionResult>();
            response.Should().BeOfType<ObjectResult>();
            logger.Received(1).LogError($"{exception.Message} : {exception.StackTrace}");
        }

And there you have it. We were able to mock HttpContext and test methods that access it. Although we had to write a few lines of code in order to make this happen, I feel like its well worth it if this allows us to easily unit test our applications. If you're interested, the following link to this github repo has all the code used in this post. https://github.com/frazchaudhry/blogprojects/tree/main/HttpContextMock