ASP.NET MVC2 自定义视图引擎在单元测试中被忽略

发布于 2024-10-06 21:55:27 字数 1399 浏览 9 评论 0原文

我正在尝试对将数据填充到 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) 让单元框架调用我的视图引擎。

I'm trying to unit test a controller that stuffs data into a ViewData. All our views require similar data (Customer info which is derived from the url). So instead of putting the call into every single controller method, the original developers chose to put this ViewData stuffing into the OnActionExecuting event.

Of course, when you invoke the controller's action from a unit test, OnActionExecuting doesn't fire. (Thanks MVC Team!)

So I tried creating a custom view engine and having it stuff the customer data into the controllerContext when the view is requested. This works fine in a browser, but my viewEngine is ignored when I run this test. No amount of ViewEngines.Add(new funkyViewEngine) has any effect.

  [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);
            }

Is there any hope here? How do I either 1) create a method that gets called on controller execution BOTH in the browser and the unit framework or 2) get the unit framework to invoke my view engine.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

风筝在阴天搁浅。 2024-10-13 21:55:27

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

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

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

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

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

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

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

我的最后一点是,您应该将功能保留在 OnActionExecuting 中。自定义视图引擎绝对是错误的地方。

Sorry for your frustration. The reason why you are seeing OnActionExecuting not being called when you directly call your action method from the unit test is because that's not how things work in MVC.

The request gets executed via a "pipeline", which as far as this area is concerned consists of the ControllerActionInvoker. This class is responsible for:

  1. Finding the action method
  2. Invoking action filters' OnActionExecuting method (note: your controller class is also an action filter)
  3. Calling the action method itself
  4. Invoking the action filters' OnActionExecuted method
  5. Handling the result (e.g. finding the view and rendering it)

In your unit test you are directly invoking step 3. and skipping all the other steps. In a unit test, it is your responsibility to call any setup code required for your action to work.

However, this does not mean you should now write unit tests that use the ControllerActionInvoker to execute the entire pipeline. We (the MVC team) have already verified that all the pieces work together.

Instead, you should test your specific application code. In this case, you might consider having the following unit tests:

  1. A test that verifies that given some Url calling OnActionExecuting on your controller puts the right Customer object into ViewData
  2. A test that verifies that given some Customer object present in ViewData your action method returns the appropriate result

My last point is that you should keep the functionality in OnActionExecuting. A custom view engine is definetely the wrong place for it.

寒尘 2024-10-13 21:55:27

这不是您可能正在寻找的答案,但我正在使用自定义 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);
    }
}

Not an answer you're probably looking for, but I'm using a custom MvcHandler to achieve the same goal (getting customer from URL in multi-tenant app). ViewEngine doesn't sound like a good place for this kind of logic to me...

My custom handler looks more or less like this:

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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文