Testing user if he has a specific authority using

2019-02-15 07:24发布

问题:

The question has been Updated for better explanation to the issue I have,

Simply I have this controller,

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

and simply I need to test the retrned viewResult of the action method IdeaCoordinator but I couldn't simply because if the _authorizationService.AuthorizeAsync validation Method, I tried to mock the method but simply I couldn't because it is an Extension Built In Method , then I tried to make a work around solution by creating a new Interface that implements IAuthorizationService and Mock that customized Interface like that

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

and Inject it to while I am calling the controller constructor and then I found myself like that :

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

I expected different results because I marked the return result to be true just to ignore this line of code and continue to the Viewresult but while I debugged my Test Method again the compiler got into the validation message as it didn't feel the changes I did on the AuthorizeAsync Method result ..

Thank you so much in advance.

==Solution==

Intro :

“We can not solve our problems with the same level of thinking that created them” - Albert Einstein. and with this adorable saying I would like to inform that I spent about 1 week for solving this problem until I felt it will never solved at this moment, I spent hours of investigation, but after reading an article and after changing the way of thinking , the solution came in 30 mins.

Problem at a glance :

Simply, I was trying to unit test the written above action method, and I have a serious problem related with that I couldn't mock the method "AuthorizeAsync" and simply because it is a built in extension method and because of extension method nature as static method it can never be mocked with the traditional way of mocking classes.

Solution in Details :

To be able to mock this action method I created a static class that contains static delegates and with those delegates I will mock or as it can be said "Wrap" my extension method by a replacement of my static delegates in my unit test class as following.

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

and at my test method I just called the mocked object in the controller constructor instantiation.

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

and as it said, The single greatest cause of happiness is gratitude.I would like to thank Stephen Fuqua for his brilliant solution and article , http://www.safnet.com/writing/tech/2014/04/making-mockery-of-extension-methods.html

Thank you everyone :) !

回答1:

Mock the necessary dependencies for the test. The method under test makes use of IAuthorizationService,IIdeaManagementService,and ITenantService. Everythign else is not needed for this particular test.

Coupling your code to 3rd party code you don't own and control makes it difficult to test. My suggestion is to abstract that behind an interface you control so you have that flexibility. So change out IAuthorizationService for your own abstraction.

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

The implementation would wrap the actual authorization service that uses the extension method

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

Make sure to register your wrapper. for example.

services.AddSingleton<ICustomAuthorizationService, CustomAuthorizationService>();

If Identity is already added to service collection then the IAuthorizationService will get injected into your custom service when resolved.

So now for your test you can mock the interfaces you control and not have to worry about breaking 3rd party code.

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

This is one example of the draw backs of extension methods as they are static and difficult to test if they hide dependencies.