如何在 UserManager Class 中模拟 GetUserAsync 方法?

How do I Mock the GetUserAsync Method Within the UserManager Class?

我正在尝试对在构造函数中采用 UserManager 对象的 Reports 控制器进行单元测试。

public class ReportController : BaseReportController {
    private readonly IUserService _userService;

    public ReportController ( IOptions<AppSettings> appSettings,
                              UserManager<ApplicationUser> userManager,
                              IUserService userService ) : base( appSettings, userManager ) {
        _userService = userService;
    }

    public async Task<ActionResult> Report ( string path ) {
        var currentUser = await GetCurrentUserAsync();
        var excludedItems = _userService.GetUserExcludedReportsById( currentUser.Id ).Select( er => er.Path );

        if ( string.IsNullOrEmpty( path ) || excludedItems.Any( path.Contains ) ) {
            return RedirectToAction( nameof(HomeController.Index), "Home" );
        }

        var customItems = _userService.GetUserCustomReportsById( currentUser.Id ).Select( er => er.Path );

        if ( path.Contains( AppSettings.CustomReportsFolderName ) && !customItems.Any( path.Contains ) ) {
            return RedirectToAction( nameof(HomeController.Index), "Home" );
        }


        var model = GetReportViewerModel( Request );
        model.Parameters.Clear();
        var dbname = _userService.GetDefaultDbName( (await GetCurrentUserAsync()).Id );
        model.Parameters.Add( "connectionStr", new[] {
            dbname
        } );
        //model.ReportPath = "/Portal Reports" + path;
        model.ReportPath = path;
        model.ClientCredentialType = System.ServiceModel.HttpClientCredentialType.Ntlm;

        return View( "Report", model );
    }
}

我想我需要以某种方式提供一个应用程序用户来调用它。因此 GetCurrentUserAsync() 将是第一个示例。这传递给基地:

public virtual Task<ApplicationUser> GetCurrentUserAsync() {
        return UserManager.GetUserAsync(HttpContext.User);
    }

我已经创建了一个 FakeUserManager,但它仍然在身份部分出现错误并出现空异常。这是我目前的测试:

[Fact]
    public void ReportControllerReturnsToIndexIfPathIsNull()
    {
        //Arrange
        var context = new Mock<HttpContext>();
        context.Setup(x => x.User).Returns(user.Object);
        var mockUserService = new Mock<IUserService>();           
        AppSettings appSettings = new AppSettings() { };
        IOptions<AppSettings> options = Options.Create(appSettings);
        var mockUserStore = new Mock<IUserStore<ApplicationUser>>();          
        var sut = new ReportController(options, new FakeUserManager(mockUserStore.Object), mockUserService.Object);
        sut.HttpContext = context.Object;         
        //Act
        var result = sut.Report("");            
        //Assert           

    }
}

public class FakeUserManager : UserManager<ApplicationUser>
{
    public FakeUserManager(IUserStore<ApplicationUser> userStore)
        : base(userStore/*new Mock<IUserStore<ApplicationUser>>().Object*/,
              new Mock<IOptions<IdentityOptions>>().Object,
              new Mock<IPasswordHasher<ApplicationUser>>().Object,
              new IUserValidator<ApplicationUser>[0],
              new IPasswordValidator<ApplicationUser>[0],
              new Mock<ILookupNormalizer>().Object,
              new Mock<IdentityErrorDescriber>().Object,
              new Mock<IServiceProvider>().Object,
              new Mock<ILogger<UserManager<ApplicationUser>>>().Object)
    {

    }

    public override Task<ApplicationUser> FindByIdAsync(string id)
    {
        return Task.FromResult(new ApplicationUser { Id = id });
    }
}

我走对了吗?我怎样才能传递这个httpcontext.user?非常感谢。

从技术上讲,您不需要模拟 GetUserAsync,如果您 实际上更好。您所需要做的就是模拟 HttpContext 以便返回正确的用户。

这是一个明显更容易跨越的障碍 并且 实际上提供了更好的测试。如果您模拟 GetUserAsync,则您正在测试某个操作的特定实现,即使用 GetUserAsync 来获取用户的操作。如果您后来决定通过某些服务 class 直接从您的上下文等获取用户,那么您的测试就会中断。

同时,只要你根据 HttpContext 查询当前用户,显然任何方法都会使用,因为那是 "current" 用户的概念所在,那么你不是绑定到任何一个实现。只要操作以某种方式获得当前用户,它就有效。

更新

var controller = new ReportController();
controller.ControllerContext = new ControllerContext
{
    HttpContext = new DefaultHttpContext
    {
        User = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
        {
            new Claim(ClaimTypes.Name, "foo")
        }));
    }
};

您可能需要根据您的代码正在执行的操作,对用户的声明设置进行一些尝试。以上用于设置 User.Identity.Name 的值,对于大多数用途应该足够了。标准声明的完整列表是 here.