如何为我的单元测试创建HttpContext ?

本文关键字:创建 HttpContext 单元测试 我的 | 更新日期: 2023-09-27 18:15:09

我正在努力模拟单元测试所需的HttpContext

我用SessionManager接口从我的Mvc控制器中抽象出会话控制,并通过一个名为CookieSessionManager的类实现了这一点。(早期开发阶段)。

CookieSessionManager使用注入的单例HttpContextAccessor来使用HttpContext(在Startup.cs ConfigureServices中)。

我正在使用Cookie认证,这是在Startup.cs中设置的app.UseCookieAuthentication

在调试模式下手动测试结果与预期一致

我为AccountController类编写的MSUnit测试与注入的MockSessionManager类一起工作。

真正的问题是我为CookieSessionManager类编写的单元测试。我已经尝试设置HttpContext,如下所示;

[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);
    }
误差

但是对sessionManager.Login(CreateValidApplicationUser());的调用似乎没有设置IsAuthenticated标志,并且测试CookieSessionManager_Login_ValidUser_Authenticated_isTrue失败。

[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;
}

测试名称:CookieSessionManager_Login_ValidUser_Authenticated_isTrue

: line 43 Test Outcome: Failed Test Duration: 0:00:00.0433169

结果StackTrace: at ClaimsWebAppTests.Identity.CookieSessionManagerTest.CookieSessionManager_Login_ValidUser_Authenticated_isTrue()

CookieSessionManagerTest.cs:第46行结果消息:断言。IsTrue失败。

我的代码

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;
        }
    }
}

如何为我的单元测试创建HttpContext ?

不幸的是,用HttpContext进行测试几乎是不可能的。它是一个密封的类,不使用任何接口,所以您不能模仿它。通常,最好的方法是抽象出与HttpContext一起工作的代码,然后只测试其他更特定于应用程序的代码。

看起来你已经通过HttpContextAccessor完成了这一点,但是你使用它不正确。首先,您暴露了HttpContext实例,这几乎破坏了整个目的。这个类应该能够自己返回类似User.Identity.IsAuthenticated的东西,比如:httpContextAccessor.IsAuthenticated。在内部,该属性将访问私有的HttpContext实例并只返回结果。

一旦以这种方式使用它,您就可以模拟HttpContextAccessor来简单地返回测试所需的内容,而不必担心为它提供HttpContext实例。

当然,这意味着仍然有一些未经测试的代码,即使用HttpContext的访问器方法,但这些通常非常直接。例如,IsAuthenticated的代码就是类似return httpContext.User.Identity.IsAuthenticated的代码。你唯一会搞砸的方式就是你用肥手指了一些东西,但是编译器会警告你。

我为我的单元测试创建了这个助手功能,这允许我测试那些需要httpRequest部分的特定方法。

public static IHttpContextAccessor GetHttpContext(string incomingRequestUrl, string host)
    {
        var context = new DefaultHttpContext();
        context.Request.Path = incomingRequestUrl;
        context.Request.Host = new HostString(host);
        //Do your thing here...
        var obj = new HttpContextAccessor();
        obj.HttpContext = context;
        return obj;
    }

这并没有直接回答问题的上下文,但它提供了一种替代测试的方法,当你开始使用它时,会让事情变得更容易。

有一个用于ASP的集成测试包。. NET Core和相关文档可以在这里找到:

https://docs.asp.net/en/latest/testing/integration-testing.html

享受吧!

你可以像下面这样创建一个继承HttpContext的Test类。并在需要的地方使用测试类。您可以在代码中添加缺失的实现。

public class TestHttpContext : HttpContext
{
    [Obsolete]
    public override AuthenticationManager Authentication
    {
        get { throw new NotImplementedException(); }
    }
    public override ConnectionInfo Connection
    {
        get { throw new NotImplementedException(); }
    }
    public override IFeatureCollection Features
    {
        get { throw new NotImplementedException(); }
    }
    public override IDictionary<object, object> Items
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }
    public override HttpRequest Request
    {
        get { throw new NotImplementedException(); }
    }
    public override CancellationToken RequestAborted
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }
    public override IServiceProvider RequestServices
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }
    HttpResponse _response;
    public override HttpResponse Response
    {
        get
        {
            if (this._response == null)
            {
                this._response = new TestHttpResponse();
                this._response.StatusCode = 999;
            }
            return this._response;
        }
    }
    public override ISession Session
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }
    public override string TraceIdentifier
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }
    public override ClaimsPrincipal User
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }
    public override WebSocketManager WebSockets
    {
        get { throw new NotImplementedException(); }
    }
    public override void Abort()
    {
        throw new NotImplementedException();
    }
}