【问题标题】:Testing user if he has a specific authority using AuthorizeAsync() in Xunit-Unit Testing在 Xunit-Unit 测试中使用 AuthorizeAsync() 测试用户是否具有特定权限
【发布时间】:2017-08-15 09:33:36
【问题描述】:

问题已更新,以便更好地解释我遇到的问题,

我有这个控制器,

    [Authorize]
    public class IdeaManagementController : Controller
    {
        private IIdeaManagementService _ideaManagementService;

        private ITenantService _tenantService;

        private ITagService _tagService;

        private IEmployeeIdeaCategoryService _ideaManagementCategoryService;

        private static PbdModule _modul = PbdModule.IdeaManagement;

        IAuthorizationService _authorizationService;

        public IdeaManagementController(
            IIdeaManagementService ideaManagementService,
            ITenantService tenantService,
            ITagService tagService,
            IAuthorizationService authorizationService,
            IEmployeeIdeaCategoryService ideaManagementCategoryService)
        {
            _ideaManagementService = ideaManagementService;
            _tenantService = tenantService;
            _tagService = tagService;
            _authorizationService = authorizationService;
            _ideaManagementCategoryService = ideaManagementCategoryService;
        }

    public async Task<IActionResult> IdeaCoordinator()
    {
        if (!await _authorizationService.AuthorizeAsync(User, "IdeaManagement_Coordinator"))
        {
            return new ChallengeResult();
        }
        var ideas = _ideaManagementService.GetByIdeaCoordinator(_tenantService.GetCurrentTenantId());
        return View(ideas);
    }
}

我只需要测试动作方法 IdeaCoordinator 的 retrned viewResult 但我不能简单地因为如果是 _authorizationService.AuthorizeAsync 验证方法,我试图模拟该方法但我不能因为它是一个扩展内置方法,然后我尝试通过创建一个实现 IAuthorizationService 的新接口并模拟该自定义接口来解决解决方案

public interface ICustomAuthorizationService : IAuthorizationService
{
    Task<bool> AuthorizeAsync(ClaimsPrincipal user, string policyName);
}


public IAuthorizationService CustomAuthorizationServiceFactory()
{
   Mock<ICustomAuthorizationService> _custom = new Mock<ICustomAuthorizationService>();
    _custom.Setup(c => c.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), "IdeaManagement_Coordinator")).ReturnsAsync(true);
    return _custom.Object;
}

并在我调用控制器构造函数时将其注入,然后我发现自己就是这样:

[Theory]
[InlineData(1)]
public async void IdeaManager_Should_Return_ViewResult(int _currentTenanatID)
{
    // Arrange ..
    _IdeaManagementControllerObject = new IdeaManagementController
                                      (IdeaManagementServiceMockFactory(_currentTenanatID),
                                      TenantServiceMockFactory(_currentTenanatID),
                                       TagServiceMockFactory(),
                                       AuthorizationServiceMockFactory(),
                                       EmployeeIdeaCategoryServiceMockFactory());
    // Act 
    var _view = await _IdeaManagementControllerObject.IdeaCoordinator() as ViewResult;

    // Assert 
    Assert.IsType(new ViewResult().GetType(), _view);
}

我期待不同的结果,因为我将返回结果标记为 true 只是为了忽略这行代码并继续查看结果,但是当我再次调试我的测试方法时,编译器进入了验证消息,因为它没有感觉到我对 AuthorizeAsync 方法结果所做的更改..

非常感谢您。

==解决方案==

简介:

“我们无法用创造问题的同样水平的思维来解决我们的问题”——阿尔伯特·爱因斯坦。并用这个可爱的说法告诉我,我花了大约 1 周的时间来解决这个问题,直到我觉得现在永远无法解决,我花了几个小时的调查,但是在阅读了一篇文章并改变了思维方式之后,解决方案在 30 分钟内出现。

问题一目了然:

简单地说,我正在尝试对上面编写的操作方法进行单元测试,但我遇到了一个严重的问题,即我无法模拟方法“AuthorizeAsync”,这仅仅是因为它是一个内置的扩展方法并且因为扩展方法本质是静态方法,它永远不能用传统的模拟类的方式来模拟。

详细解决方案:

为了能够模拟此操作方法,我创建了一个包含静态委托的静态类,并使用这些委托进行模拟,或者可以说通过在我的单元测试中替换我的静态委托来“包装”我的扩展方法类如下。

public static class DelegateFactory
{
    public static Func<ClaimsPrincipal, object, string, Task<bool>> AuthorizeAsync =
        (c, o, s) =>
        {
            return AuthorizationServiceExtensions.AuthorizeAsync(null, null, "");
        };
}

public Mock<IAuthorizationService> AuthorizationServiceMockExtensionFactory()
{
    var mockRepository = new Moq.MockRepository(Moq.MockBehavior.Strict);
    var mockFactory = mockRepository.Create<IAuthorizationService>();
    var ClaimsPrincipal = mockRepository.Create<ClaimsPrincipal>();
    mockFactory.Setup(x => x.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), It.IsAny<string>())).ReturnsAsync(true);
    return mockFactory;
}

在我的测试方法中,我只是在控制器构造函数实例化中调用了模拟对象。

    [Fact]
    public async void IdeaCoordinator_When_AuthroizedUser_IsNotNull_But_IdeaManagement_Manager_Authorized_Return_View()
    {
       // Arrange
        int approvedByID = data.GetTenantByID(1).TenantId;
        _IdeaManagementControllerObject = new IdeaManagementController
                                          (IdeaManagementServiceMockFactory().Object,
                                          TenantServiceMockFactory().Object,
                                           TagServiceMockFactory().Object,
                                           AuthorizationServiceMockExtensionFactory().Object,
                                           EmployeeIdeaCategoryServiceMockFactory().Object);
        //Act
        IdeaManagementServiceMockFactory().Setup(m => m.GetByIdeaCoordinator(approvedByID)).Returns(data.GetCordinatedEmpIdeas(approvedByID));
        ViewResult _view = await _IdeaManagementControllerObject.IdeaCoordinator() as ViewResult;
        var model = _view.Model as List<EmployeeIdea>;
        // Assert
        Assert.Equal(3, model.Count);
        Assert.IsType(new ViewResult().GetType(), _view);
    }

正如它所说,幸福的唯一最大原因是感激。我要感谢 Stephen Fuqua 的出色解决方案和文章,http://www.safnet.com/writing/tech/2014/04/making-mockery-of-extension-methods.html

谢谢大家:)!

【问题讨论】:

  • 但是您的测试需要一个挑战结果。根据被测方法,当未经授权使用时会发生这种情况。不清楚你在问什么。
  • 同时显示控制器构造函数。有很多依赖项必须为测试而模拟。 (代码气味),但需要查看这些依赖项是如何注入的,以便能够为您提供答案
  • 该问题已通过编辑控制器的构造函数以及目标模拟类的构造函数进行了更新。
  • 我试图模拟这样的方法:_authorizationService.Setup(h => h.AuthorizeAsync(It.IsAny(), "IdeaManagement_Coordinator")).ReturnsAsync (真的); ...但后来我遇到了一个异常,因为 AuthorizeAsync 是一个静态方法!
  • 我知道 :( 你对模拟这个方法有什么建议吗?

标签: c# unit-testing asp.net-core-mvc xunit.net asp.net-core-identity


【解决方案1】:

模拟测试所需的依赖项。被测方法使用IAuthorizationServiceIIdeaManagementServiceITenantService。此特定测试不需要其他所有内容。

将您的代码与您不拥有和控制的第 3 方代码相结合会导致测试变得困难。我的建议是在你控制的界面后面抽象出来,这样你就有了灵活性。所以把IAuthorizationService换成你自己的抽象。

public interface ICustomAuthorizationService {
     Task<bool> AuthorizeAsync(ClaimsPrincipal user, string policyName);
}

实现将包装使用扩展方法的实际授权服务

public class CustomAuthorizationService: ICustomAuthorizationService {
    private readonly IAuthorizationService service;

    public CustomAuthorizationService(IAuthorizationService service) {
        this.service = service;
    }

    public Task<bool> AuthorizeAsync(ClaimsPrincipal user, string policyName) {
        return service.AuthorizeAsync(user, policyName);
    }
}

确保注册您的包装器。例如。

services.AddSingleton<ICustomAuthorizationService, CustomAuthorizationService>();

如果 Identity 已添加到服务集合中,则 IAuthorizationService 将在解析时注入到您的自定义服务中。

因此,现在对于您的测试,您可以模拟您控制的接口,而不必担心破坏第 3 方代码。

[Theory]
[InlineData(1)]
public async void IdeaManager_Should_Return_ViewResult(int _currentTenanatID) {
    // Arrange ..
    var ideaManagementService = new Mock<IIdeaManagementService>();
    var tenantService = new Mock<ITenantService>();
    var authorizationService = new Mock<ICustomAuthorizationService>();
    var sut = new IdeaManagementController(
                     ideaManagementService.Object,
                     tenantService.Object,
                     null,
                     authorizationService.Object,
                     null);

     authorizationService
         .Setup(_ => _.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), "IdeaManagement_Coordinator"))
         .ReturnsAsync(true);

     tenantService
         .Setup(_ => _.GetCurrentTenantId())
         .Returns(_currentTenanatID);

     var ideas = new //{what ever is your expected return type here}
     ideaManagementService
         .Setup(_ => _.GetByIdeaCoordinator(_currentTenanatID))
         .Returns(ideas);

    // Act 
    var _view = await sut.IdeaCoordinator() as ViewResult;

    // Assert
    Assert.IsNotNull(_view);
    Assert.IsType(typeof(ViewResult), _view);
    Assert.AreEqual(ideas, view.Model);
}

这是扩展方法的缺点之一,因为它们是静态的,如果隐藏依赖项则难以测试。

【讨论】:

  • authorizationService .Setup(_ => _.AuthorizeAsync(It.IsAny(), "IdeaManagement_Coordinator")) .ReturnsAsync(true); ===>> 它会像我之前尝试过的那样抛出异常!
  • IAuthorizationService 是一个内置接口,您可以通过调用命名空间 Microsoft.AspNetCore.Authorization ..
  • 您对将痣作为模拟扩展方法的一种方式有何看法?
  • public static Task AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, string policyName); ==>> 这是 AuthorizeAsync 主体,您可以使用它作为扩展方法!
  • 它是.Net Frameowkr本身提供的内置方法当然我不知道引擎盖下发生了什么!
【解决方案2】:

我在开发 ASP.NET Core API 时也遇到了这个问题。我的情况不太复杂,所以不确定相同的解决方案是否适用于您。

这是我的解决方案

IAuthorizationService 有两个不是扩展的方法。可以假设(并且我已验证)这些扩展只是帮助程序,并且会调用这些方法之一。

Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);

所以对我来说嘲笑IAuthorizationService 就像执行以下操作一样简单:

var authorizeService = new Mock<IAuthorizationService>();
authorizeService.Setup(service => service.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), It.IsAny<string>())).ReturnsAsync(AuthorizationResult.Success);

【讨论】:

    【解决方案3】:

    要添加到 markbeij 的答案,您还必须实例化 ClaimsPrincipal 用户(可能还有其身份),否则将引发空引用异常。这是我遇到的问题(ASP.NET Core 5),这是我对遇到此问题的其他人的解决方案:

    // Arrange
    var mockAuthorizationService = new Mock<IAuthorizationService>();
    mockAuthorizationService
        .Setup(a => a.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), It.IsAny<string>()))
        .ReturnsAsync(AuthorizationResult.Success)
        .Verifiable();
    
    // Instantiate User
    var httpContext = new DefaultHttpContext();
    httpContext.User = new ClaimsPrincipal();
    
    // Add identity if you need to access the User.Identity
    httpContext.User.AddIdentity(new ClaimsIdentity());
    
    controller.ControllerContext = new ControllerContext
    {
        HttpContext = httpContext
    };
    
    var controller = new AccountController(mockAuthorizationService.Object);
    
    // Act
    var result = await controller.Index();
    
    // Assert
    mockAuthorizationService.Verify(a => a.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<object>(), It.IsAny<string>()), Times.Once);
    

    【讨论】:

      猜你喜欢
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-27
      • 2014-01-14
      • 1970-01-01
      • 1970-01-01
      • 2010-10-18
      相关资源
      最近更新 更多