在 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
的用户可能会获得bar
s 但不会获得foo
s,反之亦然,拥有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 失败