我正在尝试对我的控制器进行单元测试,并且我使用默认的MVC 3 AccountController
单元测试作为基础。到目前为止,我有我的控制器,看起来像:
public partial class HomeController : MyBaseController
{
public HomeController(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; }
public virtual ActionResult Index()
{
return View();
}
public virtual ActionResult About()
{
return View();
}
}
MyBaseController
包含以下代码:
public class MyBaseController : Controller
{
public MyJobLeadsBaseController()
{
CurrentUserId = 0;
}
protected override void Initialize(RequestContext requestContext)
{
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
// If the user is logged in, retrieve their login id
var user = MembershipService.GetUser();
if (user != null)
CurrentUserId = (int)user.ProviderUserKey;
}
public int CurrentUserId { get; set; }
public IMembershipService MembershipService { get; set; }
protected IUnitOfWork _unitOfWork;
}
当我运行实际网站时,一切正常,断点显示正确触发了Initialize()
。但是,以下单元测试从不运行Initialize(RequestContext)
方法:
[TestMethod]
public void Index_Shows_When_Not_Logged_In()
{
// Setup
HomeController controller = new HomeController(_unitOfWork);
controller.MembershipService = new MockMembershipService(null);
SetupController(controller);
// Act
ActionResult result = controller.Index();
// Verify
Assert.IsNotNull(result, "Index returned a null action result");
Assert.IsInstanceOfType(result, typeof(ViewResult), "Index did not return a view result");
}
protected static void SetupController(Controller controller)
{
RequestContext requestContext = new RequestContext(new MockHttpContext(), new RouteData());
controller.Url = new UrlHelper(requestContext);
controller.ControllerContext = new ControllerContext
{
Controller = controller,
RequestContext = requestContext
};
}
通过此单元测试进行调试表明,在任何时候都不会调用被覆盖的MyBaseController.Initialize()
。这会导致我的CurrentUserId
属性未在单元测试中设置但在实时系统上设置的问题。
我还需要做些什么才能触发Initialize()
被调用?
答案 0 :(得分:8)
当您通过网站向控制器发出请求时,MVC框架会接收该请求并通过许多不同的步骤运行它。在那个步骤流程中的某个地方,MVC知道它必须调用Initialize
方法,以便它在Initialize
类中找到MyBaseController
并执行它。在这一点上,一切都很好,一切正常。
当您在测试代码中创建HomeController
的新实例时,您只需创建一个类的新实例。您没有通过MVC请求管道运行该类,并且您的测试框架不知道执行Initialize
方法,因此您会收到错误。
我个人希望独立于Index
方法测试Initialize
操作。您上面的Index
操作是空的,因此我无法确切地看到您正在尝试做什么,但如果您为登录用户返回一个视图而另一个用于匿名用户,我会执行以下操作:
[TestMethod]
public void Index_Shows_When_Not_Logged_In(){
HomeController controller = new HomeController(_unitOfWork);
controller.CurrentUserId=0;
controller.Index();
//Check your view rendered in here
}
[TestMethod]
public void Some_Other_View_Shows_When_Logged_In(){
HomeController controller = new HomeController(_unitOfWork);
controller.CurrentUserId=12; //Could be any value
controller.Index();
//Check your view rendered in here
}
MVC contrib项目(http://mvccontrib.codeplex.com/)中有一些非常漂亮的测试助手可以让你做以下事情:
controller.Index().AssertViewRendered();
答案 1 :(得分:3)
构造函数不调用Initialize。 IIRC调用initialize的是Execute / ExecuteCore方法。
以下是MVC源代码:
protected virtual void Execute(RequestContext requestContext) {
if (requestContext == null) {
throw new ArgumentNullException("requestContext");
}
if (requestContext.HttpContext == null) {
throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
}
VerifyExecuteCalledOnce();
Initialize(requestContext);
using (ScopeStorage.CreateTransientScope()) {
ExecuteCore();
}
}
这基本上是从BeginProcessRequest方法中的MvcHandler调用的。
更新
单元测试中不存在RequestContext,因为您没有通过ASP.NET。如果我没记错的话,你需要嘲笑它。
在测试中使用Initialize的另一个问题是成员资格提供程序,我自己没有使用过,但我猜想AccountMembershipService会在测试中失败吗?在我看来,这会产生脆弱的测试。如果它必须联系服务器,它可能也会让你失望,并且可能会在CI服务器上失败。
IMO,从基本的看看Init方法,它不应该在那里。我想到的最简单的解决方案就是使用依赖注入将ControllerUserId注入Controller ctor。
答案 2 :(得分:2)
在你“运行实际站点”的情况下,我怀疑它调用了HomeController的默认构造函数,而HomeController又会调用基本控制器的相关代码。
尝试将公共HomeController(IUnitOfWork unitOfWork)的自定义ctor修改为
public HomeController(IUnitOfWork unitOfWork):base()
这将确保在调用时调用基本控制器的默认ctor。