在Xunit-Unit Testing中使用AuthorizeAsync()测试用户是否具有特定权限

Ahm*_*att 3 c# unit-testing xunit.net asp.net-core-mvc asp.net-core-identity

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

我只有这个控制器,

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

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

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

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

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

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

非常感谢你提前.

== ==解决方案

介绍 :

"我们无法用创造它们的相同思维水平来解决我们的问题" - 阿尔伯特爱因斯坦.并且用这个可爱的说法我想告诉我,我花了大约一个星期的时间来解决这个问题,直到我觉得它现在永远无法解决,我花了几个小时的调查,但在阅读了一篇文章后改变了思路,解决方案是在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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

谢谢大家 :) !

Nko*_*osi 6

模拟测试所需的依赖项.被测方法利用IAuthorizationService,IIdeaManagementServiceITenantService.此特定测试不需要其他标志.

将您的代码与您不拥有和控制的第三方代码相结合会使测试变得困难.我的建议是在你控制的界面后面抽象,这样你就具有了这种灵活性.因此,改变IAuthorizationService你自己的抽象.

public interface ICustomAuthorizationService {
     Task<bool> AuthorizeAsync(ClaimsPrincipal user, string policyName);
}
Run Code Online (Sandbox Code Playgroud)

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

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

确保注册您的包装.例如.

services.AddSingleton<ICustomAuthorizationService, CustomAuthorizationService>();
Run Code Online (Sandbox Code Playgroud)

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

所以现在为了您的测试,您可以模拟您控制的接口,而不必担心破坏第三方代码.

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

这是扩展方法缺点的一个例子,因为它们是静态的,并且如果它们隐藏依赖性则难以测试.


小智 6

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

这是我的解决方案

IAuthorizationService有两个不是扩展的方法。人们可以假设(并且我已经验证)扩展只是助手,并且将调用这些方法之一。

Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);
Run Code Online (Sandbox Code Playgroud)

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

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