Ron*_*obs 27 c# asp.net unit-testing asp.net-mvc-5
我通过添加一个新属性来扩展ApplicationUser类(如教程中所示, 使用Facebook和Google OAuth2和OpenID登录创建一个ASP.NET MVC 5应用程序(C#))
public class ApplicationUser : IdentityUser
{
public DateTime BirthDate { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
现在我想创建一个单元测试来验证我的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);
}
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
当我调用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);
}
Run Code Online (Sandbox Code Playgroud)
在最低层,样板代码包括这个花絮
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
Run Code Online (Sandbox Code Playgroud)
这是问题的根源发生的地方.这个对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
Run Code Online (Sandbox Code Playgroud)
在以前的版本中,ASP.NET MVC团队非常努力地使代码可以测试.从表面上看,现在测试AccountController并不容易.我有一些选择.
我可以
修改锅炉板代码,使其不调用扩展方法并在该级别处理此问题
设置OWin管道以进行测试
避免编写需要AuthN/AuthZ基础设施的测试代码(不是合理的选择)
我不确定哪条路更好.任何人都可以解决这个问题.我的问题归结为哪个是最好的策略.
注意:是的,我知道我不需要测试我没写过的代码.UserManager基础设施提供MVC5就是这样一个基础设施但是如果我想编写验证我对ApplicationUser的修改的测试或验证依赖于用户角色的行为的代码那么我必须使用UserManager进行测试.
Ron*_*obs 27
我正在回答我自己的问题,所以如果你认为这是一个很好的答案,我可以从社区得到一个感觉.
步骤1:修改生成的AccountController,使用支持字段为AuthenticationManager提供属性设置器.
// Add this private variable
private IAuthenticationManager _authnManager;
// Modified this from private to public and add the setter
public IAuthenticationManager AuthenticationManager
{
get
{
if (_authnManager == null)
_authnManager = HttpContext.GetOwinContext().Authentication;
return _authnManager;
}
set { _authnManager = value; }
}
Run Code Online (Sandbox Code Playgroud)
步骤2: 修改单元测试以为Microsoft.OWin.IAuthenticationManager接口添加模拟
[TestMethod]
public void Register()
{
// Arrange
var userManager = new UserManager<ApplicationUser>(new TestUserStore<ApplicationUser>());
var controller = new AccountController(userManager);
controller.SetFakeControllerContext();
// Modify the test to setup a mock IAuthenticationManager
var mockAuthenticationManager = new Mock<IAuthenticationManager>();
mockAuthenticationManager.Setup(am => am.SignOut());
mockAuthenticationManager.Setup(am => am.SignIn());
// Add it to the controller - this is why you have to make a public setter
controller.AuthenticationManager = mockAuthenticationManager.Object;
// 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);
}
Run Code Online (Sandbox Code Playgroud)
现在测试通过.
好主意?馊主意?
我的需求是相似的,但我意识到我不想对我的 AccountController 进行纯单元测试。相反,我想在尽可能接近其自然栖息地的环境中对其进行测试(如果需要,可以进行集成测试)。所以我不想模拟周围的物体,而是使用真实的物体,尽可能少地使用我自己的代码。
HttpContextBaseExtensions.GetOwinContext 方法也妨碍了我,所以我对 Blisco 的提示非常满意。现在我的解决方案最重要的部分如下所示:
/// <summary> Set up an account controller with just enough context to work through the tests. </summary>
/// <param name="userManager"> The user manager to be used </param>
/// <returns>A new account controller</returns>
private static AccountController SetupAccountController(ApplicationUserManager userManager)
{
AccountController controller = new AccountController(userManager);
Uri url = new Uri("https://localhost/Account/ForgotPassword"); // the real string appears to be irrelevant
RouteData routeData = new RouteData();
HttpRequest httpRequest = new HttpRequest("", url.AbsoluteUri, "");
HttpResponse httpResponse = new HttpResponse(null);
HttpContext httpContext = new HttpContext(httpRequest, httpResponse);
Dictionary<string, object> owinEnvironment = new Dictionary<string, object>()
{
{"owin.RequestBody", null}
};
httpContext.Items.Add("owin.Environment", owinEnvironment);
HttpContextWrapper contextWrapper = new HttpContextWrapper(httpContext);
ControllerContext controllerContext = new ControllerContext(contextWrapper, routeData, controller);
controller.ControllerContext = controllerContext;
controller.Url = new UrlHelper(new RequestContext(contextWrapper, routeData));
// We have not found out how to set up this UrlHelper so that we get a real callbackUrl in AccountController.ForgotPassword.
return controller;
}
Run Code Online (Sandbox Code Playgroud)
我还没有成功地让一切正常工作(特别是,我无法让 UrlHelper 在 ForgotPassword 方法中生成正确的 URL),但现在我的大部分需求都得到了满足。
| 归档时间: |
|
| 查看次数: |
21787 次 |
| 最近记录: |