ASP.NET 5 单元测试控制器与用户管理器

本文关键字:用户 管理器 控制器 单元测试 NET ASP | 更新日期: 2023-09-27 18:36:14

我开始使用 ASP.NET 5(vNext),现在我正在尝试对使用UserManager注册和登录用户的控制器进行单元测试。

我正在使用xUnitmoq.netcore

在我的单元测试中,我有:

var mockStore = new Mock<IUserStore<ApplicationUser>>(MockBehavior.Strict).As<IUserPasswordStore<ApplicationUser>>();
mockStore.Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), CancellationToken.None)).Returns(Task.FromResult(new IdentityResult()));
var userManager = new UserManager<ApplicationUser>(mockStore.Object, null, null, null, null, null, null, null, null, null);

以及控制器中的代码:

var result = await _securityManager.CreateAsync(user, model.Password);

但是,当我运行单元测试时,在调用 CreateAsync 时出现空异常。

我不明白为什么我不能用ApplicationUser和字符串参数模拟CreateAsync方法,我必须用CancelToken模拟它。

这是我使用 ASP.NET 5 的第一个项目,我之前使用 ASP.NET 4.6,所以现在很多事情都不一样了。

而且,你认为我应该已经在 ASP.NET 5 中开发了一个重要的项目,还是应该等待 4.6 ASP.NET 开发它?

ASP.NET 5 单元测试控制器与用户管理器

在尝试了很多事情之后,我想出了一个可行的解决方案。

用户管理器

我创建了一个 FakeUserManager 类,该类继承了 UserManager 并覆盖了 CreateAsync 方法。

public class FakeUserManager : UserManager<ApplicationUser>
{
    public FakeUserManager()
        : base(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,
              new Mock<IHttpContextAccessor>().Object)
    { }
    public override Task<IdentityResult> CreateAsync(ApplicationUser user, string password)
    {
        return Task.FromResult(IdentityResult.Success);
    }
}

然后我将FakeUserManager的新实例传递给AccountController构造函数,它工作正常。


登录管理器

对于那些可能需要模拟SignInManager的人,我通过以下方式做到了:

我创建了一个 FakeSignInManager 类,该类继承了 SignInManager 并覆盖了我需要的方法。

public class FakeSignInManager : SignInManager<ApplicationUser>
{
    public FakeSignInManager(IHttpContextAccessor contextAccessor)
        : base(new FakeUserManager(),
              contextAccessor,
              new Mock<IUserClaimsPrincipalFactory<ApplicationUser>>().Object,
              new Mock<IOptions<IdentityOptions>>().Object,
              new Mock<ILogger<SignInManager<ApplicationUser>>>().Object)
    {
    }
    public override Task SignInAsync(ApplicationUser user, bool isPersistent, string authenticationMethod = null)
    {
        return Task.FromResult(0);
    }
    public override Task<SignInResult> PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure)
    {
        return Task.FromResult(SignInResult.Success);
    }
    public override Task SignOutAsync()
    {
        return Task.FromResult(0);
    }
}
由于SignInManager

需要一个上下文访问器才能工作,我让FakeSignInManager在构造函数中接收它。

然后,在创建 SignInManager 的新实例之前,我通过以下方式准备一个新的 HttpContextAccessor:

var context = new Mock<HttpContext>();
var contextAccessor = new Mock<IHttpContextAccessor>();
contextAccessor.Setup(x => x.HttpContext).Returns(context.Object);

然后创建新的 FakeSignInManager 实例:

new FakeSignInManager(contextAccessor.Object);