【问题标题】:Render a View during a Unit Test - ControllerContext.DisplayMode在单元测试期间渲染视图 - ControllerContext.DisplayMode
【发布时间】:2015-03-21 04:29:27
【问题描述】:

我正在开发一个生成大型复杂报告的 ASP.NET MVC 4 Web 应用程序。我想编写渲染视图的单元测试,以确保视图不会因模型而爆炸:

 [Test]
 public void ExampleTest(){                  
     var reportModel = new ReportModel();

     var reportHtml = RenderRazorView(
           @"..\..\Report.Mvc\Views\Report\Index.cshtml", 
           reportModel);

     Assert.IsFalse(
         string.IsNullOrEmpty(reportHtml),
         "View Failed to Render!");          
 }

 public string RenderRazorView(string viewPath, object model){
    //WHAT GOES HERE?
 }

我在网络上看到了很多关于此的信息,但它要么反对测试竞争,要么只能在网络请求的上下文中使用。

  • 反对 - Unit Testing the Views? - 得出的结论是视图中不应该存在任何逻辑,因此您只需要测试编译即可。我认为测试视图以确保没有空引用异常、显示正确的部分等是有价值的。
  • Web 请求的上下文 - Render a view as a string - 这是为了呈现要在电子邮件中发送的视图。但这种方法需要通过网络请求(即有效的HttpContextBase)调用。

我一直在努力使 Render a view as a string 与 Mocked HttpContextBase 一起工作,但在使用 Mocked ControllerContext 时遇到了问题:

对象引用未设置为对象的实例。 在 System.Web.WebPages.DisplayModeProvider.GetDisplayMode(HttpContextBase 上下文) 在 System.Web.Mvc.ControllerContext.get_DisplayMode() 在 System.Web.Mvc.VirtualPathProviderViewEngine.GetPath(ControllerContext controllerContext, String[] locations, String[] areaLocations, String locationsPropertyName, String name, String controllerName, String cacheKeyPrefix, Boolean useCache, String[]& searchedLocations)

这是我目前的代码:

    public string RenderRazorView(string viewPath, object model)
    {
        var controller = GetMockedDummyController();

        //Exception here
        var viewResult = 
            ViewEngines.Engines.FindView(controller.ControllerContext, "Index", "");

        using (var sw = new StringWriter())
        {
            var viewContext =
                new ViewContext(
                    controller.ControllerContext,
                    viewResult.View,
                    new ViewDataDictionary(model),
                    new TempDataDictionary(),
                    sw);

            viewResult.View.Render(viewContext, sw);

            return sw.ToString();
        }
    }

我正在构建控制器:

    private Controller GetMockedDummyController()
    {
        var HttpContextBaseMock = new Mock<HttpContextBase>();
        var HttpRequestMock = new Mock<HttpRequestBase>();
        var HttpResponseMock = new Mock<HttpResponseBase>();
        HttpContextBaseMock.SetupGet(x => x.Request).Returns(HttpRequestMock.Object);
        HttpContextBaseMock.SetupGet(x => x.Response).Returns(HttpResponseMock.Object);

        var controller = new DummyController();

        var routeData = new RouteData();
        routeData.Values.Add("controller", "Dummy");

        controller.ControllerContext = 
            new ControllerContext(
                HttpContextBaseMock.Object,
                routeData,
                controller);

        controller.Url =
            new UrlHelper(
                new RequestContext(
                    HttpContextBaseMock.Object,
                    routeData), 
                new RouteCollection());

        return controller;
    }

DummyController 就是 public class DummyController : Controller {}

问题

给出视图的路径,我如何将它从测试项目呈现为 HTML?或者更具体地说,我怎样才能模拟出ControllerContext.DisplayMode

【问题讨论】:

  • 我遇到了类似的问题。您最终找到解决方案了吗?我也想知道如何模拟 ControllerContext.DisplayMode。
  • 不幸的是,我从来没有找到一个好的解决方案来做到这一点,如果我没记错的话,我不得不放弃我的努力,因为我通过了我的 Research Spike 并且没有返回任何有用的东西。您可能想看看一些新的 ASP.NET Core 内容,因为那里的测试故事可能要好得多。他们所做的部分工作是,AFAIK 将HttpContext 隔离出来,这是这里的核心问题之一。

标签: c# asp.net-mvc asp.net-mvc-4 unit-testing rhino-mocks


【解决方案1】:

您将需要一个空的 Controller 仅用于测试(例如 TestController

public class WebMvcHelpers
{
    public static string GetViewPageHtml(object model, string viewName)
    {
        System.Web.Mvc.Controller controller = CreateController<TestController>();

        ViewEngineResult result = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);

        if (result.View == null)
            throw new Exception(string.Format("View Page {0} was not found", viewName));

        controller.ViewData.Model = model;
        StringBuilder sb = new StringBuilder();
        using (StringWriter sw = new StringWriter(sb))
        {
            using (System.Web.UI.HtmlTextWriter output = new System.Web.UI.HtmlTextWriter(sw))
            {
                ViewContext viewContext = new ViewContext(controller.ControllerContext, result.View, controller.ViewData, controller.TempData, output);
                result.View.Render(viewContext, output);
            }
        }

        return sb.ToString();
    }

    /// <summary>
    /// Creates an instance of an MVC controller from scratch 
    /// when no existing ControllerContext is present       
    /// </summary>
    /// <typeparam name="T">Type of the controller to create</typeparam>
    /// <returns></returns>
    public static T CreateController<T>(RouteData routeData = null)
                where T : System.Web.Mvc.Controller, new()
    {
        T controller = new T();

        // Create an MVC Controller Context
        HttpContextBase wrapper = null;
        if (HttpContext.Current != null)
            wrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
        //else
        //    wrapper = CreateHttpContextBase(writer);


        if (routeData == null)
            routeData = new RouteData();

        if (!routeData.Values.ContainsKey("controller") && !routeData.Values.ContainsKey("Controller"))
            routeData.Values.Add("controller", controller.GetType().Name
                                                        .ToLower()
                                                        .Replace("controller", ""));

        controller.ControllerContext = new System.Web.Mvc.ControllerContext(wrapper, routeData, controller);
        return controller;
    }
}

public class TestController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

【讨论】:

  • 这不起作用。它仍然需要HttpContext.Current。为什么CreateHttpContextBase 方法被注释掉了?你有实现吗?
  • 嗯,不,我不知道那个方法是从哪里来的。但是you can mock HttpContext.Current 实例
【解决方案2】:

假设您已经完全分离了关注点,是否有必要实例化控制器?如果没有,那么也许您可以使用RazorEngine 来测试您的观点。

var contents = File.ReadAllText("pathToView"); 
var result = Razor.Parse(contents,model);
// assert here

【讨论】:

  • 不,我不需要调用控制器或其方法。我已经有一个模型,想用它来渲染一个视图,所以我试试看!
  • 你有没有用它来渲染“MVC”视图。我无法让它编译像@Styles.Render() 这样的语句。引擎抱怨它缺少程序集,即使我已经添加了它们。查看编译后的输出后,似乎正在基于 web.config system.web\pages\namespaces 添加 using 语句。有没有办法添加这些?
  • RazorEngine 链接中有文档讨论如何处理程序集。是的,我已将其用于视图和字符串模板。我还没有用它来渲染嵌套模板。
  • 这里是 RazorEngine 文档的直接链接。 antaris.github.io/RazorEngine
  • 您可以使用自定义引用解析器 antaris.github.io/RazorEngine/ReferenceResolver.html 加载您自己的程序集
猜你喜欢
  • 2019-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多