对继承自 BasePageModel 的 Razor 页面进行单元测试

Lau*_*son 1 c# unit-testing asp.net-core razor-pages

我有一个 Razor Pages 应用程序,其中站点中的所有页面都使用多项服务BasePageModel,并将这些服务添加到我在每个 Razor 页面上继承的 。我的BasePageModel看起来像这样:

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

Razor 页面本身看起来像这样:

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

我正在创建单元测试来测试 Razor 页面上的授权。我的授权具有取决于用户声明的策略。我想做的是模拟用户声明并相应地测试授权是否成功或失败,具体取决于页面。但是,我无法UserClaimsService从我的测试中模拟它。我实例化了 pageModel 但无法获取任何值BasePageModel(例如UserClaimsService)。知道我该怎么做吗?

Nko*_*osi 5

应避免通过请求服务使用服务定位器反模式。通过构造函数注入遵循显式依赖原则

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

Razor 页面本身将变成

public class IndexModel : BasePageModel {
    public IndexModel(IAuthorizationService authService) : base (authService) {
        //...
    }

    public async Task<ActionResult> OnGet() {
        if (!HasAccess("PermissionName")) {
            return new ForbidResult();
        }
        return Page();
    }
}
Run Code Online (Sandbox Code Playgroud)

对于现在的测试,可以根据隔离单元测试的需要模拟显式依赖关系

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

ASP.NET Core 中的参考Razor Pages 单元测试

您似乎还混合了涉及用户声明和授权的跨领域关注点,但我想这超出了当前要求的范围。

参考ASP.NET Core 中的 Razor Pages 授权约定