【问题标题】:Why does my asp.net mvc controller not call it's base class' Initialize method during unit tests?为什么我的 asp.net mvc 控制器在单元测试期间不调用它的基类的 Initialize 方法?
【发布时间】:2023-04-01 04:03:01
【问题描述】:

我正在尝试对我的控制器进行单元测试,并且我使用默认的 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() 被调用?

【问题讨论】:

    标签: c# asp.net-mvc unit-testing controller


    【解决方案1】:

    当您通过网站向控制器发出请求时,MVC 框架会获取该请求并通过许多不同的步骤运行它。在该步骤管道中的某个地方,MVC 知道它必须调用 Initialize 方法,以便在您的 MyBaseController 类中找到 Initialize 并执行它。此时一切正常,一切正常。

    当您在测试代码中创建HomeController 的新实例时,您只是在创建一个类的新实例。您没有通过 MVC 请求管道运行该类,并且您的测试框架不知道要执行 Initialize 方法,因此您会收到错误。

    我个人希望独立于Initialize 方法来测试Index 操作。您上面的 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 贡献项目 (http://mvccontrib.codeplex.com/) 中有一些非常漂亮的测试助手,可以让您执行以下操作:

    controller.Index().AssertViewRendered();
    

    【讨论】:

      【解决方案2】:

      构造函数不调用 Initialize。 IIRC 调用初始化的是 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 方法来看,它不应该存在。在不破坏任何东西的情况下,想到的最简单的解决方案是使用依赖注入将 CurrentUserId 注入 Controller ctor。

      【讨论】:

        【解决方案3】:

        在您“运行实际站点”的情况下,我怀疑它会调用 HomeController 的默认构造函数,而后者又会调用基本控制器的对应 ctor。

        尝试将 public HomeController(IUnitOfWork unitOfWork) 的自定义 ctor 修改为

        公共 HomeController(IUnitOfWork unitOfWork):base()

        这将确保在调用基本控制器的默认 ctor 时调用它。

        【讨论】:

        • 我的HomeController 中没有默认构造函数。唯一的构造函数是采用IUnitOfWork 的构造函数。你不能打电话给new HomeController()
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多