在 ASP.NET Core MVC API 控制器上对 AuthorizeAttribute 进行单元测试

Posted

技术标签:

【中文标题】在 ASP.NET Core MVC API 控制器上对 AuthorizeAttribute 进行单元测试【英文标题】:Unit testing an AuthorizeAttribute on an ASP.NET Core MVC API controller 【发布时间】:2018-02-01 12:07:02 【问题描述】:

我有一个带有需要进行单元测试的控制器的 ASP.NET Core MVC API。

控制器:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace TransitApi.Api.Controllers

    [Route("api/foo")]
    public class FooController : Controller
    
        private IFooRepository FooRepository  get; 

        public FooController(IFooRepository fooRepository)
        
            FooRepository = fooRepository;
        

        [HttpGet]
        [Authorize("scopes:getfoos")]
        public async Task<IActionResult> GetAsync()
        
            var foos = await FooRepository.GetAsync();
            return Json(foos);
        
    

我必须能够对AuthorizeAttribute 的有效性进行单元测试。我们的代码库中存在属性缺失和范围不正确的问题。 This answer 正是我正在寻找的,但在 Microsoft.AspNetCore.Mvc.Controller 中没有 ActionInvoker 方法意味着我不能这样做。

单元测试:

[Fact]
public void GetAsync_InvalidScope_ReturnsUnauthorizedResult()

    // Arrange
    var fooRepository = new StubFooRepository();
    var controller = new FooController(fooRepository)
    
        ControllerContext = new ControllerContext
        
            HttpContext = new FakeHttpContext()
            // User unfortunately not available in HttpContext
            //,User = new User()  Scopes = "none" 
        
    ;

    // Act
    var result = controller.GetAsync().Result;

    // Assert
    Assert.IsType<UnauthorizedResult>(result);

我如何对没有正确范围的用户进行单元测试,以防止其访问我的控制器方法?

目前我只测试AuthorizeAttribute 的存在,如下所示,但这确实不够好:

    [Fact]
    public void GetAsync_Analysis_HasAuthorizeAttribute()
    
        // Arrange
        var fooRepository = new StubFooRepository();
        var controller = new FooController(fooRepository)
        
            ControllerContext = new ControllerContext
            
                HttpContext = new FakeHttpContext()
            
        ;

        // Act
        var type = controller.GetType();
        var methodInfo = type.GetMethod("GetAsync", new Type[]  );
        var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true);

        // Assert
        Assert.True(attributes.Any());
    

【问题讨论】:

这需要使用内存测试服务器进行集成测试。 为什么测试AuthorizeAttribute 的存在不够好? AuthorizeAttribute 既是属性又是 IAuthorizationFilter。属性部分不任何事情,它只是元数据。 MVC 的单元测试保证如果它存在,它将被注册为当前请求和逻辑运行的授权过滤器。如果您使用的是AuthorizeAttribute 的子类,那么测试它的逻辑是有意义的,但是由于您不是唯一需要测试的是属性的存在及其属性的配置(Users 和@ 987654334@). 对于第二个 NightOwl888 的评论,我已经创建了扫描我所有控制器操作的测试,以确保它们都定义了一些明确的授权,无论是 AllowAnonymous 还是 Authorized。请注意,它是针对 MVC5 的,我仍然必须将其移植到核心。 @Ivan - 如果您需要除少数授权之外的所有方法,那么您可以在启动时全局注册 AuthorizeAttribute,然后使用AllowAnonymous 覆盖行为。这样,它们默认情况下被锁定,您不必担心以后的更改会丢失。或者,您可以创建自己的自定义 IAuthorizationFilter 全局注册,以管理整个应用程序的安全性(甚至可能是您自己的属性来执行某些操作),然后可以将其作为一个单独的部分进行测试,而不是您的控制器和操作。 用户拥有允许他们单独访问方法的范围。例如,拥有scope:bar 的用户可能会获得bars 但不会获得foos,反之亦然,拥有scope:all 的用户可以同时访问两者。这就是为什么测试这些属性如此重要的部分原因。 【参考方案1】:

这需要使用内存测试服务器进行集成测试,因为框架在处理请求管道时会评估该属性。

Integration testing in ASP.NET Core

集成测试可确保应用程序的组件在组装在一起时能够正常运行。 ASP.NET Core 支持使用单元测试框架和内置测试 Web 主机进行集成测试,可用于处理请求而无需网络开销。

[Fact]
public async Task GetAsync_InvalidScope_ReturnsUnauthorizedResult() 
    // Arrange
    var server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
    var client = server.CreateClient();
    var url = "api/foo";
    var expected = HttpStatusCode.Unauthorized;

    // Act
    var response = await client.GetAsync(url);

    // Assert
    Assert.AreEqual(expected, response.StatusCode);

如果您不希望测试触及实际的生产实现,您还可以专门为测试创建一个启动,它将用存根/模拟替换 DI 的任何依赖项。

【讨论】:

谢谢 - 我们已将集成测试设置为 Postman 集合。我们将使用这些来测试范围。【参考方案2】:

您可以做的是配置您的测试服务器以添加匿名过滤器中间件:

private HttpClient CreatControllerClient()

        return _factory.WithWebHostBuilder(builder
            => builder.ConfigureTestServices(services =>
            
                // allow anonymous access to bypass authorization
                services.AddMvc(opt => opt.Filters.Add(new AllowAnonymousFilter()));
            )).CreateClient();

【讨论】:

【参考方案3】:

首先删除 IAuthorizationHandler

var authorizationDescriptor = services.FirstOrDefault(d => d.ServiceType == typeof(IAuthorizationHandler));
                if (authorizationDescriptor != null)
                    services.Remove(authorizationDescriptor);

然后添加

services.AddScoped<IAuthorizationHandler, TestAllowAnonymous>();


public class TestAllowAnonymous : IAuthorizationHandler
        
            public Task HandleAsync(AuthorizationHandlerContext context)
            
                foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList())
                    context.Succeed(requirement); //Simply pass all requirementsreturn Task.CompletedTask;
                return Task.CompletedTask;
            


        

【讨论】:

以上是关于在 ASP.NET Core MVC API 控制器上对 AuthorizeAttribute 进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Web API 控制器添加到现有的 ASP.NET Core MVC?

如何从 ASP.NET MVC 到 ASP.NET Core Web API 的 PUT?

从 MVC 到使用 ASP.NET Core 6.0 的Minimal API

对象反序列化在 Asp Net Core MVC 控制器中的 POST 失败

使用 ASP.NET Core MVC 创建 Web API

ASP.NET MVC Core WebAPI 项目不返回 html