Search code examples
c#unit-testingasp.net-corerazor-pages

Unit Testing Razor Pages that inherit from a BasePageModel


I have a Razor Pages app where several services are used by all pages in the site and have added those services to a BasePageModel, that I inherit from on each Razor Page. My BasePageModel looks something like this:

public abstract class BasePageModel : PageModel
{
    private IUserClaimsService _userClaims;
    private IAuthorizationService _authService;

    protected virtual IUserClaimsService UserClaimsService => _userClaims ?? (_userClaims = HttpContext.RequestServices.GetService<IUserClaimsService>());
    protected virtual IAuthorizationService AuthService => _authService ?? (_authService = HttpContext.RequestServices.GetService<IAuthorizationService>()); 
}

The Razor Page itself would look something like this:

public class IndexModel : BasePageModel
{
    public async Task<ActionResult> OnGet()
    {
        var HasAccess = (await AuthService.AuthorizeAsync(User, "PermissionName")).Succeeded;
        if (!HasAccess)
        {
            return new ForbidResult();
        }
        return Page();
    }
}

I am creating unit tests to test authorization on my Razor Pages. My authorization has policies that depend on user claims. What I'd like to do is to mock the user claims and test whether authorization succeeds or fails accordingly, depending on the page. However, I am not able to get to the UserClaimsService to mock it from my test. I instantiate the pageModel but am unable to get to any of the BasePageModel values (e.g. UserClaimsService). Any idea how I might go about doing this?


Solution

  • The service locator anti pattern used via the request services should be avoided. Follow Explicit Dependency Principle via constructor injection.

    public abstract class BasePageModel : PageModel {
        protected readonly IAuthorizationService authService;
    
        protected BasePageModel(IAuthorizationService authService) {
            this.authService = authService;
        }
        
        protected async Task<bool> HasAccess(string permissionName) {
            return (await authService.AuthorizeAsync(User, permissionName)).Succeeded;
        }
    }
    

    The Razor page itself would then become

    public class IndexModel : BasePageModel {
        public IndexModel(IAuthorizationService authService) : base (authService) {
            //...
        }
    
        public async Task<ActionResult> OnGet() {
            if (!HasAccess("PermissionName")) {
                return new ForbidResult();
            }
            return Page();
        }
    }
    

    For testing now the explicit dependencies can be mocked as needed for isolated unit tests

    public async Task Index_Should_Be_Forbidden() {
        // Arrange
        var authService = Mock.Of<IAuthorizationService>(_ =>
            _.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), null, It.IsAny<string>()) == 
                Task.FromResult(AuthorizationResult.Failed())
        );
    
        //Create test user with what ever claims you want
        var displayName = "UnAuthorized User name";
        var identity = new GenericIdentity(displayName);
        var principle = new ClaimsPrincipal(identity);
        // use default context with user
        var httpContext = new DefaultHttpContext() {
            User = principle
        }
        //need these as well for the page context
        var modelState = new ModelStateDictionary();
        var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
        var modelMetadataProvider = new EmptyModelMetadataProvider();
        var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
        // need page context for the page model
        var pageContext = new PageContext(actionContext) {
            ViewData = viewData
        };
        //create model with necessary dependencies
        var model = new IndexModel(authService) {
            PageContext = pageContext //context includes User 
        };
    
        //Act
        var result = await model.OnGet();
        
        //Assert
        result.Should()
            .NotBeNull()
            .And.BeOfType<ForbidResult>();
    }
    

    Reference Razor Pages unit tests in ASP.NET Core

    You also appear to be mixing cross cutting concerns where it comes to the user claims and authorization but I guess that is outside of the scope of what is currently being asked.

    Reference Razor Pages authorization conventions in ASP.NET Core