【问题标题】:ASP.NET MVC2 Custom View Engine ignored in unit testsASP.NET MVC2 自定义视图引擎在单元测试中被忽略
【发布时间】:2010-12-10 16:57:14
【问题描述】:

我正在尝试对将数据填充到 ViewData 中的控制器进行单元测试。我们所有的视图都需要类似的数据(来自 url 的客户信息)。因此,最初的开发人员没有将调用放入每个控制器方法中,而是选择将此 ViewData 填充到 OnActionExecuting 事件中。

当然,当您从单元测试中调用控制器的操作时,不会触发 OnActionExecuting。 (感谢 MVC 团队!)

所以我尝试创建一个自定义视图引擎,并在请求视图时将客户数据填充到 controllerContext 中。这在浏览器中运行良好,但是当我运行这个测试时,我的 viewEngine 被忽略了。 ViewEngines.Add(new funkyViewEngine) 没有任何效果。

  [TestMethod()]
            public void LoginTest()
            {
                ViewEngines.Engines.Clear();
                ViewEngines.Engines.Add(new FunkyViewEngine());

                UserController target = new UserController();
                target.SetStructureMap();  <--sets us to use the test repo

                target.ControllerContext.HttpContext = MVCHelpers.FakeHttpContext("https://customersubdomain.ourdomain.com");  <--moq magic
                var actual = target.Login();
                Assert.IsTrue(actual.GetType().IsAssignableFrom(typeof(System.Web.Mvc.ViewResult)));
                var  vr = actual as ViewResult;
                Assert.IsTrue(vr.ViewData.Community() != null);  <--"Community" should be set by viewengine
                Assert.IsTrue(vr.ViewData.Community().Subdomain == "customersubdomain.ourdomain");
                Assert.IsTrue(vr.ViewData.Community().CanRegister);
            }

这里有希望吗?我该如何 1) 创建一个在浏览器和单元框架中的控制器执行时都被调用的方法,或者 2) 让单元框架调用我的视图引擎。

【问题讨论】:

    标签: unit-testing asp.net-mvc-2 viewengine


    【解决方案1】:

    很抱歉让您失望了。当您直接从单元测试中调用您的操作方法时,您看到 OnActionExecuting 未被调用的原因是因为这不是 MVC 中的工作方式。

    请求通过“管道”执行,就该区域而言,该管道由ControllerActionInvoker 组成。这个类负责:

    1. 寻找动作方法
    2. 调用动作过滤器的OnActionExecuting 方法(注意:您的控制器类也是一个动作过滤器)
    3. 调用操作方法本身
    4. 调用操作过滤器的OnActionExecuted 方法
    5. 处理结果(例如,查找并渲染视图)

    在您的单元测试中,您直接调用第 3 步并跳过所有其他步骤。在单元测试中,您有责任调用操作所需的任何设置代码。

    但是,这并不意味着您现在应该编写使用ControllerActionInvoker 来执行整个管道的单元测试。我们(MVC 团队)已经验证了所有部分都可以协同工作。

    相反,您应该测试您的特定应用程序代码。在这种情况下,您可以考虑进行以下单元测试:

    1. 验证给定 URL 调用 OnActionExecuting 在您的控制器上将正确的客户对象放入 ViewData 的测试
    2. 验证给定 ViewData 中存在的某些 Customer 对象的测试,您的操作方法返回适当的结果

    我的最后一点是你应该保留OnActionExecuting 中的功能。自定义视图引擎绝对不适合它。

    【讨论】:

    • 是的,ViewEngine 的事情是一个 hack,它可以绕过这样一个事实,即这种行为使得除了微不足道的控制器之外的所有控制器都完全无法测试。我可以放置一个为我调用 OnActionExecuting 和 OnActionExecuted 的公共包装器并手动调用它,但这对我的 RoleFilter、SessionFilter、RequireHttps 等属性没有帮助。
    • 您应该单独测试构成您的应用程序的组件并相信 :) 该框架将使一切协同工作。例如,对于过滤器属性,您可以只使用反射来验证该属性是否已在您的方法中声明。 Mvc 被设计为可测试的,唯一困难的时候是您尝试测试框架本身。当您遇到此类问题时,这是一个很好的指标,表明您可能需要采取不同的方法。
    • 我无法使用单元测试来验证 MyController.Action 是否正确地将 http 重定向到 https 并让用户登录,这仍然完全被打破了。那些试图将他们的团队拖向 TDD 并大喊大叫的人服务很差由 MVC 团队破坏我们的测试。
    • 我的论点是,如果你试图验证端到端的行为,它不再是单元测试的领域,而是功能测试的领域。我也不同意“MVC 团队削弱我们的测试”的说法(也许是因为我是 MVC 团队的一员 :)。我们已经付出了很多努力来公开许多可扩展性点。但是,如果您确实想测试框架本身的端到端行为,您可以在单元测试中使用ControllerActionInvoker。但我认为,这样做是在不必要地花费你的努力,而这些努力可以更好地花在其他地方。
    • 所以 Assert.IsTrue(MyController.Login() is typeof(RedirectResult)) 在我使用 [RequireHttps] 时不是单元测试,但如果我在我的方法正文中手动编写了 https 检测?这听起来像是一场宗教争论。底线是:如果不使用“你不需要使用这个”ControllerActionInvoker,我就无法对我的代码进行自动化测试。这太疯狂了。
    【解决方案2】:

    不是您可能正在寻找的答案,但我正在使用自定义 MvcHandler 来实现相同的目标(从多租户应用程序中的 URL 获取客户)。对我来说,ViewEngine 听起来不太适合这种逻辑......

    我的自定义处理程序看起来或多或少像这样:

    public class AccountMvcHandler : MvcHandler
    {
        public Account Account { get; private set; }
    
        protected override IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state)
        {
            return base.BeginProcessRequest(httpContext, callback, state);
        }
    
        protected override IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
        {
            string accountName = this.RequestContext.RouteData.GetRequiredString("account");
            Account = ServiceFactory.GetService<ISecurityService>().GetAccount(accountName);
    
            return base.BeginProcessRequest(httpContext, callback, state);
        }
    }
    

    【讨论】:

    • 你能澄清一下吗? ViewEngine hack 只是对单元测试方法中缺少动作布线的一个丑陋的后备。如果有更好的方法,我很乐意听到。
    猜你喜欢
    • 1970-01-01
    • 2017-04-05
    • 1970-01-01
    • 2010-10-04
    • 2011-02-16
    • 2018-04-12
    • 2011-02-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多