如何为单元测试创​​建HttpContext?

Edw*_*eau 3 c# asp.net-mvc unit-testing asp.net-core

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

我用一个SessionManager接口从我的Mvc控制器中抽象出对会话的控制,并用一个叫做的类来实现它CookieSessionManager.(早期发展阶段).

CookieSessionManager使用HttpContext注入的singleton HttpContextAccessor(在Startup.cs ConfigureServices中).

我正在使用在Startup.cs中设置的Cookie身份验证app.UseCookieAuthentication.

在调试模式下手动测试可以按预期工作

MSUnit我为我AccountController班级编写的测试工作是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);
    }
Run Code Online (Sandbox Code Playgroud)

错误

但调用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;
}
Run Code Online (Sandbox Code Playgroud)

测试名称:CookieSessionManager_Login_ValidUser_Authenticated_isTrue

:第43行测试结果:测试持续时间失败:0:00:00.0433169

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

CookieSessionManagerTest.cs:第46行结果消息:Assert.IsTrue失败.

我的代码

SessionManager

using ClaimsWebApp.Models;

namespace ClaimsWebApp.Identity
{
    public interface SessionManager
    {
        bool isAuthenticated();

        void Login(ApplicationUser applicationUser);

        void Logout();
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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?}");
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

War*_*avy 11

我为单元测试创​​建了这个辅助功能,这使我能够测试那些需要 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;
    }
Run Code Online (Sandbox Code Playgroud)

  • 我认为这当然应该更多地强调一下:在 AspNetCore 中,似乎获取 DefaultHttpContext() 然后添加您想要的所有内容是对您想要提供的代码进行单元测试时的正确方法。 HttpContext 的实例,其中包含一些值。defaultHttpContext 还会添加一个 defaultHttpRequest,您也可以对其进行类似的修饰。DefaultHttpRequest 本身是一个密封类(因此它本身不能直接实例化)。对于 Net Core,这显然是要走的路。 (2认同)

Chr*_*att 8

不幸的是,用它进行测试几乎是不可能的HttpContext.它是一个密封的类,不使用任何接口,所以你不能嘲笑它.通常,最好的办法是抽象出可以使用的代码,HttpContext然后测试其他更多特定于应用程序的代码.

看起来你已经完成了这个过程HttpContextAccessor,但你正在错误地使用它.首先,你暴露了HttpContext实例,这几乎完全违背了整个目的.这个类应该能够返回类似的东西User.Identity.IsAuthenticated,如:httpContextAccessor.IsAuthenticated.在内部,属性将访问私有HttpContext实例并返回结果.

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

当然,这意味着仍然有一些未经测试的代码,即使用的访问器方法HttpContext,但这些通常非常简单.例如,代码IsAuthenticated就像是return httpContext.User.Identity.IsAuthenticated.你要搞砸的唯一方法就是如果你发脾气,但编译器会警告你.

  • @ChrisPratt 这个问题是关于 ASP.NET Core,OP 使用 `DefaultHttpContext`,它是抽象基类 [`HttpContext`](https://github.com/aspnet/HttpAbstractions/blob/1ff417f3216e4fa0eb87e9adacd2379450fae524/src/ Microsoft.AspNetCore.Http.Abstractions/HttpContext.cs),完全可以在单元测试中模拟。但是,您关于更集中测试的观点是有效的。 (2认同)