集成测试中的 MVC 策略覆盖

K. *_*res 5 c# asp.net testing xunit

我正在为 MVC 应用程序添加集成测试。我们的许多端点都应用了策略,例如

namespace WorkProject
{
  [Route("A/Route")]
  public class WorkController : Controller
  {
    [HttpPost("DoStuff")]
    [Authorize(Policy = "CanDoStuff")]
    public IActionResult DoStuff(){/* */}
  }
}
Run Code Online (Sandbox Code Playgroud)

对于我们的集成测试,我已经覆盖WebApplicationFactoryASP .NET Core 文档中建议的内容。我的目标是通过创建一个允许所有各方通过授权策略的类来重载身份验证步骤并绕过策略。

namespace WorkApp.Tests
{
    public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup: class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            base.ConfigureWebHost(builder);
            builder.ConfigureServices(services =>
            {
                services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = "Test Scheme"; // has to match scheme in TestAuthenticationExtensions
                    options.DefaultChallengeScheme = "Test Scheme";
                }).AddTestAuth(o => { });


                services.AddAuthorization(options =>
                {
                    options.AddPolicy("CanDoStuff", policy =>
                        policy.Requirements.Add(new CanDoStuffRequirement()));
                });

             // I've also tried the line below, but neither worked
             // I figured that maybe the services in Startup were added before these
             // and that a replacement was necessary
             // services.AddTransient<IAuthorizationHandler, CanDoStuffActionHandler>();
             services.Replace(ServiceDescriptor.Transient<IAuthorizationHandler, CanDoStuffActionHandler>());
            });
        }
    }

    internal class CanDoStuffActionHandler : AuthorizationHandler<CanDoStuffActionRequirement>
    {
        public CanDoStuffActionHandler()
        {
        }

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanDoStuffActionRequirement requirement)
        {
            context.Succeed(requirement);

            return Task.CompletedTask;
        }
    }

    internal class CanDoStuffRequirement : IAuthorizationRequirement
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

我对服务所做的第一件事是按照此处的建议覆盖身份验证(没有关于覆盖的内容,Startup因为这似乎对我不起作用)。我倾向于相信这种身份验证覆盖有效。当我运行测试时,我从 xUnit 测试框架中收到一个 HTTP 403。如果我从 PostMan 访问我正在测试的路由,我会收到一个 HTTP 401。我还创建了一个位于自定义 Web 应用程序工厂中的类,该类允许对CanDoStuff授权处理程序。我认为这将允许通过授权策略进行集成测试,但是,如上所述,我收到一个 HTTP 403。我知道如果应用程序不知道某些文件在哪里,将返回 403。然而,这是一个严格用于接收和处理数据的 post 路由,并且该路由不会尝试返回任何视图,因此这个 403 很可能与授权策略有关,由于某种原因,它没有被覆盖。

我显然做错了什么。当我在调试模式下运行测试并在HandleRequirementsAsync函数中设置断点时,应用程序永远不会中断。是否有其他方法可以尝试覆盖授权策略?

K. *_*res 7

这就是我所做的。

  1. 用我自己的覆盖WebApplicationFactory。注意,我仍然添加了应用程序的启动作为模板参数
  2. 创建我的启动函数,该函数将覆盖我添加的ConfigureAuthServices函数。
  3. 告诉函数中的构建器ConfigureWebHost使用我的自定义启动类。
  4. ConfigureWebHost通过覆盖函数中的身份验证步骤builder.ConfigureServices
  5. 添加对控制器的程序集引用,我试图在函数末尾命中该控制器的builder.ConfigureServices端点ConfigureWebHost
  6. 我自己编写了IAuthorizationHandler允许所有请求成功的策略。

我希望我已经很好地解释了我所做的事情。如果没有,希望下面的示例代码足够容易理解。

YourController.cs

namespace YourApplication
{
  [Route("A/Route")]
  public class WorkController : Controller
  {
    [HttpPost("DoStuff")]
    [Authorize(Policy = "CanDoStuff")]
    public IActionResult DoStuff(){/* */}
  }
}
Run Code Online (Sandbox Code Playgroud)

测试.cs

namespace YourApplication.Tests
{
    public class Tests
        : IClassFixture<CustomWebApplicationFactory<YourApplication.Startup>>
    {
        private readonly CustomWebApplicationFactory<YourApplication.Startup> _factory;

        public Tests(CustomWebApplicationFactory<YourApplication.Startup> factory)
        {
            _factory = factory;
        }

        [Fact]
        public async Task SomeTest()
        {
            var client = _factory.CreateClient();
            var response = await client.PostAsync("/YourEndpoint");
            response.EnsureSuccessStatusCode();

            Assert.Equal(/* whatever your condition is */);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

CustomWebApplicationFactory.cs

namespace YourApplication.Tests
{
    public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup: class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            base.ConfigureWebHost(builder);
            builder.ConfigureServices(services =>
            {
                services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = "Test Scheme"; // has to match scheme in TestAuthenticationExtensions
                    options.DefaultChallengeScheme = "Test Scheme";
                }).AddTestAuth(o => { });


                services.AddAuthorization(options =>
                {
                    options.AddPolicy("CanDoStuff", policy =>
                        policy.Requirements.Add(new CanDoStuffRequirement()));
                });

             services.AddMvc().AddApplicationPart(typeof(YourApplication.Controllers.YourController).Assembly);
             services.AddTransient<IAuthorizationHandler, CanDoStuffActionHandler>();
            });
            builder.UseStartup<TestStartup>();
        }
    }

    internal class CanDoStuffActionHandler : AuthorizationHandler<CanDoStuffActionRequirement>
    {
        public CanDoStuffActionHandler()
        {
        }

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanDoStuffActionRequirement requirement)
        {
            context.Succeed(requirement);

            return Task.CompletedTask;
        }
    }

    internal class CanDoStuffRequirement : IAuthorizationRequirement
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

测试启动.cs

namespace YourApplication.Tests
{
    public class TestStartup : YourApplication.Startup
    {
        public TestStartup(IConfiguration configuration) : base(configuration)
        {

        }

        protected override void ConfigureAuthServices(IServiceCollection services)
        {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)