简体   繁体   English

为什么我的asp.net mvc控制器在单元测试期间没有调用它的基类'Initialize方法?

[英]Why does my asp.net mvc controller not call it's base class' Initialize method during unit tests?

I am attempting to unit test my controllers, and I am using the default MVC 3 AccountController unit tests as a basis. 我正在尝试对我的控制器进行单元测试,并且我使用默认的MVC 3 AccountController单元测试作为基础。 So far I have my controller, which looks like: 到目前为止,我有我的控制器,看起来像:

public partial class HomeController : MyBaseController
{
    public HomeController(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; }

    public virtual ActionResult Index()
    {

        return View();
    }

    public virtual ActionResult About()
    {
        return View();
    }
}

MyBaseController has the following code: 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;
}

Everything works correctly when I run the actual site, and breakpoints show that the Initialize() is correctly being triggered. 当我运行实际站点时,一切正常,断点显示正确触发了Initialize() However, the following unit test never runs the Initialize(RequestContext) method: 但是,以下单元测试从不运行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
        };
    }

Debugging through this unit test shows that at no point does the overridden MyBaseController.Initialize() get called at all. 通过此单元测试进行调试表明,在任何时候都不会调用重写的MyBaseController.Initialize() This causes issues where my CurrentUserId property is not being set in unit tests, but is being set on the live system. 这会导致我的CurrentUserId属性未在单元测试中设置但在实时系统上设置的问题。

What else do I have to do to trigger the Initialize() to be called? 还有什么办法可以触发Initialize()来调用?

When you make a request to a controller through a website the MVC framework picks up that request and runs it through a number of different steps. 当您通过网站向控制器发出请求时,MVC框架会接收该请求并通过许多不同的步骤运行它。 Somewhere in that pipeline of steps MVC knows that it must call the Initialize method so it finds the Initialize in your MyBaseController class and executes it. 在那个步骤流程中的某个地方,MVC知道它必须调用Initialize方法,以便在MyBaseController类中找到Initialize并执行它。 At this point all is well and everything works. 在这一点上一切都很顺利,一切正常。

When you create a new instance of your HomeController in your test code you're simply creating a new instance of a class. 在测试代​​码中创建HomeController的新实例时,您只需创建一个类的新实例。 You're not running that class through the MVC request pipeline and your test framework doesn't know to execute the Initialize method so you get an error. 您没有通过MVC请求管道运行该类,并且您的测试框架不知道执行Initialize方法因此您收到错误。

Personally I would look to test the Index action independently of the Initialize method. 就个人而言,我希望独立于Initialize方法测试Index操作。 Your Index action above is empty so I can't see exactly what you're trying to do but if you are returning one view for logged in users and another for anonymous users I'd do something like this: 您上面的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
}

There are some pretty nifty test helpers in the MVC contrib project ( http://mvccontrib.codeplex.com/ ) which allow you to do things like: MVC contrib项目( http://mvccontrib.codeplex.com/ )中有一些非常漂亮的测试帮助程序,它们允许您执行以下操作:

controller.Index().AssertViewRendered();

The constructor does not call Initialize. 构造函数不会调用Initialize。 IIRC What calls initialize is the Execute/ExecuteCore method. IIRC调用initialize的是Execute / ExecuteCore方法。

Here is the code from the MVC source: 以下是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();
            }
        }

This is basically called from the MvcHandler in the BeginProcessRequest method. 这基本上是从BeginProcessRequest方法中的MvcHandler调用的。

Update: 更新:

RequestContext does not exist in a unit test, because you're not going through ASP.NET. 单元测试中不存在RequestContext,因为您没有通过ASP.NET。 If I remember correctly you'd need to mock it. 如果我没记错的话,你需要嘲笑它。

Another problem in using Initialize in a test is the membership provider, which I haven't used myself, but I would guess that AccountMembershipService would fail you in a test? 在测试中使用Initialize的另一个问题是成员资格提供程序,我自己没有使用过,但我猜想AccountMembershipService会在测试中失败吗? It seems to me that this would create fragile tests. 在我看来,这会产生脆弱的测试。 It would probably also slow you down if it has to contact a server, and might fail you on a CI server. 如果它必须联系服务器,它可能也会减慢你的速度,并且可能会在CI服务器上失败。

IMO,from a basic look around the Init method, it shouldn't be there. IMO,从基本看看Init方法,它不应该存在。 The easiest solution,without breaking anything, that comes to mind is using dependency injection to inject the CurrentUserId in the Controller ctor. 想到的最简单的解决方案是使用依赖注入来在Controller ctor中注入CurrentUserId。

In the case where you "run the actual site" I suspect it calls the default constructor of your HomeController which in turn will call the base controller's correpsonding ctor. 在你“运行实际站点”的情况下,我怀疑它调用了HomeController的默认构造函数,而HomeController又会调用基本控制器的correpsonding ctor。

Try modifying your custom ctor of public HomeController(IUnitOfWork unitOfWork) to 尝试修改公共HomeController(IUnitOfWork unitOfWork)的自定义ctor

public HomeController(IUnitOfWork unitOfWork):base() public HomeController(IUnitOfWork unitOfWork):base()

This will make sure to call the base controller's default ctor whenever it is called. 这将确保在调用时调用基本控制器的默认ctor。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM