我正在通过添加一个新属性来扩展 ApplicationUser 类(如教程 Create an ASP.NET MVC 5 App with Facebook and Google OAuth2 and OpenID Sign-on (C#) 中所示)
public class ApplicationUser : IdentityUser
{
public DateTime BirthDate { get; set; }
}
现在我想创建一个单元测试来验证我的 AccountController 是否正确保存了 BirthDate。
我创建了一个名为 TestUserStore 的内存用户存储
[TestMethod]
public void Register()
{
// Arrange
var userManager = new UserManager<ApplicationUser>(new TestUserStore<ApplicationUser>());
var controller = new AccountController(userManager);
// This will setup a fake HttpContext using Moq
controller.SetFakeControllerContext();
// Act
var result =
controller.Register(new RegisterViewModel
{
BirthDate = TestBirthDate,
UserName = TestUser,
Password = TestUserPassword,
ConfirmPassword = TestUserPassword
}).Result;
// Assert
Assert.IsNotNull(result);
var addedUser = userManager.FindByName(TestUser);
Assert.IsNotNull(addedUser);
Assert.AreEqual(TestBirthDate, addedUser.BirthDate);
}
controller.Register 方法是 MVC5 生成的样板代码,但出于参考目的,我将其包含在此处。
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName, BirthDate = model.BirthDate };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
当我调用 Register 时,它会调用 SignInAsync,这就是问题所在。
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
在最低层,样板代码包括这个花絮
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
这就是问题的根源所在。对 GetOwinContext 的调用是一种扩展方法,我无法模拟,也无法用存根替换(当然,除非我更改了样板代码)。
当我运行这个测试时,我得到了一个异常
Test method MVCLabMigration.Tests.Controllers.AccountControllerTest.Register threw exception:
System.AggregateException: One or more errors occurred. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.HttpContextBaseExtensions.GetOwinEnvironment(HttpContextBase context)
at System.Web.HttpContextBaseExtensions.GetOwinContext(HttpContextBase context)
at MVCLabMigration.Controllers.AccountController.get_AuthenticationManager() in AccountController.cs: line 330
at MVCLabMigration.Controllers.AccountController.<SignInAsync>d__40.MoveNext() in AccountController.cs: line 336
在以前的版本中,ASP.NET MVC 团队非常努力地使代码可测试。从表面上看,现在测试 AccountController 似乎并不容易。我有一些选择。
我可以
修改样板代码,使其不调用扩展方法并在该级别处理此问题
设置 OWin 管道以进行测试
避免编写需要 AuthN / AuthZ 基础设施的测试代码(不是一个合理的选择)
我不确定哪条路更好。任何一个都可以解决这个问题。我的问题归结为最佳策略。
注意:是的,我知道我不需要测试不是我写的代码。提供 MVC5 的 UserManager 基础设施就是这样一个基础设施,但是如果我想编写测试来验证我对 ApplicationUser 的修改或验证取决于用户角色的行为的代码,那么我必须使用 UserManager 进行测试。