Проверка пользователя, если у него есть определенные полномочия, с использованием AuthorizeAsync() в Xunit-Unit Testing

Вопрос был обновлен для лучшего объяснения проблемы, с которой я столкнулся,

Просто у меня есть этот контроллер,

    [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);
    }
}

и просто мне нужно проверить повторное представление viewResult метода действия IdeaCoordinator, но я не мог просто потому, что если метод проверки _authorizationService.AuthorizeAsync, я пытался смоделировать метод, но просто не смог, потому что это встроенное расширение Затем в Method я попытался обойти решение, создав новый интерфейс, который реализует 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;
}

и внедрить его, пока я вызываю конструктор контроллера, а затем я оказался таким:

[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);
}

Я ожидал других результатов, потому что я пометил возвращаемый результат как истинный, просто чтобы проигнорировать эту строку кода и перейти к Viewresult, но пока я снова отлаживал свой метод тестирования, компилятор попал в сообщение проверки, поскольку он не чувствовал изменений, которые я сделал в результате метода 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);
    }

и, как говорится, «Единственная величайшая причина счастья — это благодарность». Я хотел бы поблагодарить Стивена Фукуа за его блестящее решение и статью http://www.safnet.com/writing/tech/2014/04/making-mockery-of-extension-methods.html< /а>

Всем спасибо :) !


person Ahmed Elbatt    schedule 23.03.2017    source источник
comment
Но ваш тест ожидает результат вызова. Что в соответствии с тестируемым методом происходит, когда использование не разрешено. Непонятно, что вы спрашиваете.   -  person Nkosi    schedule 23.03.2017
comment
Покажите также конструктор контроллера. есть много зависимостей, которые нужно смоделировать для теста. (запах кода), но нужно посмотреть, как внедряются эти зависимости, чтобы дать вам ответ   -  person Nkosi    schedule 23.03.2017
comment
Вопрос был обновлен путем редактирования конструктора контроллера, а также конструктора целевого класса Mocked.   -  person Ahmed Elbatt    schedule 23.03.2017
comment
Я попытался издеваться над таким методом: ... но затем я получил исключение, поскольку AuthorizeAsync является статическим методом!   -  person Ahmed Elbatt    schedule 23.03.2017
comment
Я знаю :( Есть ли у вас какие-либо предложения по издевательству над этим методом?   -  person Ahmed Elbatt    schedule 23.03.2017
comment
Абстрагируйте его за службу экземпляра. Я предположил, что он не был статичным, основываясь на вашем коде.   -  person Nkosi    schedule 23.03.2017
comment
Я был бы очень признателен, если бы вы могли показать, как это сделать на примере!   -  person Ahmed Elbatt    schedule 23.03.2017


Ответы (3)


Смоделируйте необходимые зависимости для теста. Тестируемый метод использует IAuthorizationService, IIdeaManagementService и ITenantService. Все остальное не нужно для этого конкретного теста.

Связывание вашего кода со сторонним кодом, которым вы не владеете и не контролируете, затрудняет тестирование. Мое предложение состоит в том, чтобы абстрагироваться от того, что находится за интерфейсом, которым вы управляете, чтобы у вас была такая гибкость. Так что замените 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>();

Если удостоверение уже добавлено в набор услуг, то 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);
}

Это один из примеров недостатков методов расширения, поскольку они статичны и их трудно тестировать, если они скрывают зависимости.

person Nkosi    schedule 23.03.2017
comment
авторизацияService .Setup(_ =› _.AuthorizeAsync(It.IsAny‹IPrincipal›(), IdeaManagement_Coordinator)) .ReturnsAsync(true); ===›› он выдаст исключение, подобное тому, что я пробовал раньше! - person Ahmed Elbatt; 23.03.2017
comment
IAuthorizationService — это встроенный интерфейс, который вы можете получить, вызвав NameSpace Microsoft.AspNetCore.Authorization.. - person Ahmed Elbatt; 23.03.2017
comment
каково ваше мнение о родинках как способе издевательства над методами расширения? - person Ahmed Elbatt; 23.03.2017
comment
public static Task‹bool› AuthorizeAsync (данная служба IAuthorizationService, пользователь ClaimsPrincipal, строка policyName); ==›› это тело AuthorizeAsync, и, как вы понимаете, это метод расширения! - person Ahmed Elbatt; 23.03.2017
comment
это встроенный метод, предоставленный самим .Net Frameowkr, конечно, я не могу знать, что происходит под капотом! - person Ahmed Elbatt; 23.03.2017
comment
Исходный код есть в сети. Но именно по этой причине мы не связываем наш код с 3-й частью. затрудняет тестирование. Мое предложение состоит в том, чтобы абстрагироваться от того, что находится за интерфейсом, которым вы управляете, чтобы у вас была такая гибкость. - person Nkosi; 23.03.2017
comment
Я еще раз обновил свой вопрос, могу ли я попросить вас прочитать его еще раз? - person Ahmed Elbatt; 23.03.2017
comment
@FreedomDeveloper, не наследуйте свой собственный сервис из 3-й части. вы столкнетесь с той же проблемой. оберните третью сторону своей реализацией. - person Nkosi; 23.03.2017
comment
Если бы я это сделал, мне нужно было бы создать объект CustomClass в качестве параметра в конструкторе контроллера, и я, к сожалению, не хочу ничего менять в классах кода. - person Ahmed Elbatt; 23.03.2017
comment
Тогда я думаю, вы застряли. Ничего другого я не могу предложить, если это так. - person Nkosi; 23.03.2017
comment
Давайте продолжим это обсуждение в чате. - person Ahmed Elbatt; 23.03.2017
comment
Могу я попросить вас взглянуть на решение, которое я добавил в свой обновленный вопрос? - и пожалуйста, если вам нравится решение, я был бы так признателен, если вы оцените его, чтобы администрация могла снять запрет мне писать больше вопросов?! - person Ahmed Elbatt; 03.04.2017

Я также столкнулся с этой проблемой при разработке 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);
person markbeij    schedule 14.04.2020

Чтобы добавить к ответу 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);
person HussainAlazzani    schedule 30.01.2021