C# 单元测试ASP.NET MVC5应用程序
我通过添加一个新属性(如教程所示)来扩展ApplicationUser类 ) 现在我想创建一个单元测试来验证AccountController是否正确保存了生日 我已经创建了一个名为TestUserStore的内存中用户存储C# 单元测试ASP.NET MVC5应用程序,c#,asp.net,unit-testing,asp.net-mvc-5,C#,Asp.net,Unit Testing,Asp.net Mvc 5,我通过添加一个新属性(如教程所示)来扩展ApplicationUser类 ) 现在我想创建一个单元测试来验证AccountController是否正确保存了生日 我已经创建了一个名为TestUserStore的内存中用户存储 [TestMethod] public void Register() { // Arrange var userManager = new UserManager<ApplicationUser>(new TestUserStore<Ap
[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);
}
在最底层,样板代码包含了这个小贴士
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
测试方法MVCLabMigration.Tests.Controllers.AccountControllerTest.Register引发异常:
System.AggregateException:发生一个或多个错误。-->System.NullReferenceException:对象引用未设置为对象的实例。
位于System.Web.HttpContextBaseExtensions.GetowinenEnvironment(HttpContextBase上下文)
位于System.Web.HttpContextBaseExtensions.GetOwinContext(HttpContextBase上下文)
在AccountController.cs中的MVCLabMigration.Controllers.AccountController.get_AuthenticationManager()处:第330行
在AccountController.cs中的MVCLabMigration.Controllers.AccountController.d_u40.MoveNext()处:第336行
在以前的版本中,ASP.NET MVC团队非常努力地使代码可测试。表面上看来,现在测试AccountController并不容易。我有一些选择
我能
注意:是的,我知道我不需要测试我没有编写的代码。MVC5提供的UserManager基础设施就是这样一个基础设施,但是如果我想编写测试来验证我对ApplicationUser的修改,或者编写代码来验证依赖于用户角色的行为,那么我必须使用UserManager进行测试。我在回答我自己的问题,这样如果你认为这是正确的,我就可以从社区中获得感觉一个好答案 步骤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; }
}
第二步:
修改单元测试以添加Microsoft.OWin.iaAuthenticationManager界面的模拟
[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);
}
[TestMethod]
公开作废登记册()
{
//安排
var userManager=newusermanager(newtestuserstore());
var控制器=新的AccountController(userManager);
controller.SetFakeControllerContext();
//修改测试以设置模拟IAAuthenticationManager
var mockAuthenticationManager=new Mock();
mockAuthenticationManager.Setup(am=>am.SignOut());
mockAuthenticationManager.Setup(am=>am.SignIn());
//将其添加到控制器-这就是为什么您必须创建公共setter
controller.AuthenticationManager=mockAuthenticationManager.Object;
//表演
var结果=
控制器.寄存器(新的RegisterViewModel
{
生日=测试生日,
UserName=TestUser,
Password=TestUserPassword,
ConfirmPassword=TestUserPassword
}).结果;
//断言
Assert.IsNotNull(结果);
var addedUser=userManager.FindByName(TestUser);
Assert.IsNotNull(addedUser);
AreEqual(TestBirthDate,addedUser.BirthDate);
}
现在测试通过了
好主意?坏主意?我使用了一个与您类似的解决方案—模拟IAAuthenticationManager—但我的登录代码位于LoginManager类中,该类通过构造函数注入获取IAAuthenticationManager
public LoginHandler(HttpContextBase httpContext, IAuthenticationManager authManager)
{
_httpContext = httpContext;
_authManager = authManager;
}
我正在使用注册我的依赖项:
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<HttpContextBase>(
new InjectionFactory(_ => new HttpContextWrapper(HttpContext.Current)));
container.RegisterType<IOwinContext>(new InjectionFactory(c => c.Resolve<HttpContextBase>().GetOwinContext()));
container.RegisterType<IAuthenticationManager>(
new InjectionFactory(c => c.Resolve<IOwinContext>().Authentication));
container.RegisterType<ILoginHandler, LoginHandler>();
// Further registrations here...
}
公共静态无效注册表类型(IUnityContainer容器)
{
container.RegisterType(
新的InjectionFactory(=>newhttpcontextwrapper(HttpContext.Current));
RegisterType(新的InjectionFactory(c=>c.Resolve().GetOwinContext());
container.RegisterType(
新的InjectionFactory(c=>c.Resolve().Authentication));
container.RegisterType();
//在这里进一步注册。。。
}
但是,我想测试我的Unity注册,如果不伪造(a)HttpContext.Current(足够硬)和(b)GetOwinContext(),这是很棘手的,正如您所发现的,这是不可能直接完成的
我以Phil Haack的形式找到了一个解决方案,并对HttpContext进行了一些操作,以创建一个基本的。到目前为止,我发现设置一个伪Owin变量就足以使GetOwinContext()正常工作,但是YMMV
public static class HttpSimulatorExtensions
{
public static void SimulateRequestAndOwinContext(this HttpSimulator simulator)
{
simulator.SimulateRequest();
Dictionary<string, object> owinEnvironment = new Dictionary<string, object>()
{
{"owin.RequestBody", null}
};
HttpContext.Current.Items.Add("owin.Environment", owinEnvironment);
}
}
[TestClass]
public class UnityConfigTests
{
[TestMethod]
public void RegisterTypes_RegistersAllDependenciesOfHomeController()
{
IUnityContainer container = UnityConfig.GetConfiguredContainer();
HomeController controller;
using (HttpSimulator simulator = new HttpSimulator())
{
simulator.SimulateRequestAndOwinContext();
controller = container.Resolve<HomeController>();
}
Assert.IsNotNull(controller);
}
}
公共静态类HttpSimulatorExtensions
{
公共静态void模拟器RequestStandowInContext(此HttpSimulator模拟器)
{
simulator.SimulateRequest();
字典owinenEnvironment=新字典()
{
{“owin.RequestBody”,null}
};
HttpContext.Current.Items.Add(“owin.Environment”,owinevironment);
}
}
[测试类]
公共类UnityConfigTests
{
[测试方法]
public void RegisterTypes\u registerAllDependenciesOfHomeController()
{
IUnityContainer容器=UnityConfig.GetConfiguredContainer();
家庭控制器;
使用(HttpSimulator=new HttpSimulator())
{
simulator.SimulateRequestAndOwinContext();
controller=container.Resolve();
}
Assert.IsNotNull(控制器);
}
}
如果你的SetFakeControllerContext()方法能完成这项工作,那么HttpSimulator可能会有些过分,但它看起来是集成测试的有用工具。我的需求与此类似,但我意识到我不想要一个纯粹的un
public LoginHandler(HttpContextBase httpContext, IAuthenticationManager authManager)
{
_httpContext = httpContext;
_authManager = authManager;
}
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<HttpContextBase>(
new InjectionFactory(_ => new HttpContextWrapper(HttpContext.Current)));
container.RegisterType<IOwinContext>(new InjectionFactory(c => c.Resolve<HttpContextBase>().GetOwinContext()));
container.RegisterType<IAuthenticationManager>(
new InjectionFactory(c => c.Resolve<IOwinContext>().Authentication));
container.RegisterType<ILoginHandler, LoginHandler>();
// Further registrations here...
}
public static class HttpSimulatorExtensions
{
public static void SimulateRequestAndOwinContext(this HttpSimulator simulator)
{
simulator.SimulateRequest();
Dictionary<string, object> owinEnvironment = new Dictionary<string, object>()
{
{"owin.RequestBody", null}
};
HttpContext.Current.Items.Add("owin.Environment", owinEnvironment);
}
}
[TestClass]
public class UnityConfigTests
{
[TestMethod]
public void RegisterTypes_RegistersAllDependenciesOfHomeController()
{
IUnityContainer container = UnityConfig.GetConfiguredContainer();
HomeController controller;
using (HttpSimulator simulator = new HttpSimulator())
{
simulator.SimulateRequestAndOwinContext();
controller = container.Resolve<HomeController>();
}
Assert.IsNotNull(controller);
}
}
/// <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;
}