How do I create an HttpContext for my unit test?

asp.net-core, asp.net-mvc, c#, unit-testing

Solution

Unfortunately, it's pretty much impossible to test with `HttpContext`. It's a sealed class that doesn't utilize any interfaces, so you cannot mock it. Usually, your best bet is to abstract away the code that works with `HttpContext`, and then test just your other, more application-specific code.

It looks like you've sort of already done this via `HttpContextAccessor`, but you're utilizing it incorrectly. First, you're exposing the `HttpContext` instance, which pretty much defeats the entire purpose. This class should be able to return something like `User.Identity.IsAuthenticated` on its own, like: `httpContextAccessor.IsAuthenticated`. Internally, the property would access the private `HttpContext` instance and just return the result.

Once you're utilizing it in this way, you can then mock `HttpContextAccessor` to simply return what you need for your tests, and you don't have to worry about supplying it with an `HttpContext` instance.

Granted, this means there's still some untested code, namely, the accessor methods that work with `HttpContext`, but these are generally very straight-forward. For example, the code for `IsAuthenticated` would just be something like `return httpContext.User.Identity.IsAuthenticated`. The only way you're going to screw that up is if you fat-finger something, but the compiler will warn you about that.

Problem

I am struggling to simulate the required `HttpContext` for my unit tests. I have abstracted control of the session away from my Mvc controller with a `SessionManager` interface and implemented this with a class called `CookieSessionManager`. (early development stages). `CookieSessionManager` uses the `HttpContext` by using the injected singleton `HttpContextAccessor` (in Startup.cs ConfigureServices). I'm using Cookie Authentication which is setup in Startup.cs with `app.UseCookieAuthentication`. Testing this manually in debug mode works as expected The `MSUnit` tests I have written for my `AccountController` class work with a `MockSessionManager` class injected. The real issue I have is with the unit tests I have written for my `CookieSessionManager` class. I have tried to setup the `HttpContext` as shown below; ``` [TestClass] public class CookieSessionManagerTest { private IHttpContextAccessor contextAccessor; private HttpContext context; private SessionManager sessionManager; [TestInitialize] public void Setup_CookieSessionManagerTest() { context = new DefaultHttpContext(); contextAccessor = new HttpContextAccessor(); contextAccessor.HttpContext = context; sessionManager = new CookieSessionManager(contextAccessor); } ``` The Error But the call to `sessionManager.Login(CreateValidApplicationUser());` doesn't appear to set the `IsAuthenticated` flag and the test `CookieSessionManager_Login_ValidUser_Authenticated_isTrue` fails. ``` [TestMethod] public void CookieSessionManager_Login_ValidUser_Authenticated_isTrue() { sessionManager.Login(CreateValidApplicationUser()); Assert.IsTrue(sessionManager.isAuthenticated()); } public ApplicationUser CreateValidApplicationUser() { ApplicationUser applicationUser = new ApplicationUser(); applicationUser.UserName = "ValidUser"; //applicationUser.Password = "ValidPass"; return applicationUser; } ``` Test Name: CookieSessionManager_Login_ValidUser_Authenticated_isTrue : line 43 Test Outcome: Failed Test Duration: 0:00:00.0433169 Result StackTrace: at ClaimsWebAppTests.Identity.CookieSessionManagerTest.CookieSessionManager_Login_ValidUser_Authenticated_isTrue() CookieSessionManagerTest.cs:line 46 Result Message: Assert.IsTrue failed. MY CODE SessionManager ``` using ClaimsWebApp.Models; namespace ClaimsWebApp.Identity { public interface SessionManager { bool isAuthenticated(); void Login(ApplicationUser applicationUser); void Logout(); } } ``` CookieSessionManager ``` using ClaimsWebApp.Identity; using ClaimsWebApp.Models; using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.Security.Claims; namespace ClaimsWebApp { public class CookieSessionManager : SessionManager { private List<ApplicationUser> applicationUsers; private IHttpContextAccessor ContextAccessor; private bool IsAuthenticated; public CookieSessionManager(IHttpContextAccessor contextAccessor) { this.IsAuthenticated = false; this.ContextAccessor = contextAccessor; IsAuthenticated = ContextAccessor.HttpContext.User.Identity.IsAuthenticated; applicationUsers = new List<ApplicationUser>(); applicationUsers.Add(new ApplicationUser { UserName = "ValidUser" }); } public bool isAuthenticated() { return IsAuthenticated; } public void Login(ApplicationUser applicationUser) { if (applicationUsers.Find(m => m.UserName.Equals(applicationUser.UserName)) != null) { var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, applicationUser.UserName) }, "MyCookieMiddlewareInstance"); var principal = new ClaimsPrincipal(identity); ContextAccessor.HttpContext.Authentication.SignInAsync("MyCookieMiddlewareInstance", principal); IsAuthenticated = ContextAccessor.HttpContext.User.Identity.IsAuthenticated; } else { throw new Exception("User not found"); } } public void Logout() { ContextAccessor.HttpContext.Authentication.SignOutAsync("MyCookieMiddlewareInstance"); IsAuthenticated = ContextAccessor.HttpContext.User.Identity.IsAuthenticated; } } } ``` Startup.cs ``` using ClaimsWebApp.Identity; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace ClaimsWebApp { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddScoped<SessionManager, CookieSessionManager>(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); app.UseCookieAuthentication(new CookieAuthenticationOptions() { AuthenticationScheme = "MyCookieMiddlewareInstance", LoginPath = new PathString("/Account/Unauthorized/"), AccessDeniedPath = new PathString("/Account/Forbidden/"), AutomaticAuthenticate = true, AutomaticChallenge = true }); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Account}/{action=Login}/{id?}"); }); } } } ``` CookieSessionManagerTest.cs ``` using ClaimsWebApp; using ClaimsWebApp.Identity; using ClaimsWebApp.Models; using Microsoft.AspNetCore.Http; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace ClaimsWebAppTests.Identity { [TestClass] public class CookieSessionManagerTest { private IHttpContextAccessor contextAccessor; private HttpContext context; private SessionManager sessionManager; [TestInitialize] public void Setup_CookieSessionManagerTest() { context = new DefaultHttpContext(); contextAccessor = new HttpContextAccessor(); contextAccessor.HttpContext = context; sessionManager = new CookieSessionManager(contextAccessor); } [TestMethod] public void CookieSessionManager_Can_Be_Implemented() { Assert.IsInstanceOfType(sessionManager, typeof(SessionManager)); } [TestMethod] public void CookieSessionManager_Default_Authenticated_isFalse() { Assert.IsFalse(sessionManager.isAuthenticated()); } [TestMethod] public void CookieSessionManager_Login_ValidUser_Authenticated_isTrue() { sessionManager.Login(CreateValidApplicationUser()); Assert.IsTrue(sessionManager.isAuthenticated()); } public ApplicationUser CreateValidApplicationUser() { ApplicationUser applicationUser = new ApplicationUser(); applicationUser.UserName = "ValidUser"; //applicationUser.Password = "ValidPass"; return applicationUser; } public ApplicationUser CreateInValidApplicationUser() { ApplicationUser applicationUser = new ApplicationUser(); applicationUser.UserName = "InValidUser"; //applicationUser.Password = "ValidPass"; return applicationUser; } } } ```

Original source