ASP.NET 5 带有 UserManager 的单元测试控制器
ASP.NET 5 Unit Test controller with UserManager
我开始使用 ASP.NET 5 (vNext),现在我正在尝试对使用 UserManager 来注册和登录用户的控制器进行单元测试。
我正在使用 xUnit 和 moq.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 方法,我必须用 CancellationToken 模拟它。
这是我第一个使用ASP.NET5的项目,我之前使用的是ASP.NET4.6,所以现在很多东西都不一样了。
而且,您认为我应该在 ASP.NET 5 中开发一个重要项目,还是应该等待 ASP.NET 4.6 开发它?
在尝试了很多事情之后,我想出了一个可行的解决方案。
用户管理器
我创建了一个 FakeUserManager class,它继承了 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 class,它继承了 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);
我开始使用 ASP.NET 5 (vNext),现在我正在尝试对使用 UserManager 来注册和登录用户的控制器进行单元测试。
我正在使用 xUnit 和 moq.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 方法,我必须用 CancellationToken 模拟它。
这是我第一个使用ASP.NET5的项目,我之前使用的是ASP.NET4.6,所以现在很多东西都不一样了。
而且,您认为我应该在 ASP.NET 5 中开发一个重要项目,还是应该等待 ASP.NET 4.6 开发它?
在尝试了很多事情之后,我想出了一个可行的解决方案。
用户管理器
我创建了一个 FakeUserManager class,它继承了 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 class,它继承了 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);