在单元测试期间渲染视图 - ControllerContext.DisplayMode

时间:2015-01-22 11:34:51

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

我正在开发一个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? - 结论在视图中应该没有逻辑,所以你只需要测试编译。我认为测试View是有价值的,以确保没有Null引用异常,显示正确的部分等。
  • Web请求的上下文 - Render a view as a string - 这是为了呈现要在电子邮件中发送的视图。但是这种方法需要通过Web请求(即有效的HttpContextBase)来调用。

我一直在努力调整Render a view as a string以使用Mocked HttpContextBase,但在使用Mocked ControllerContext时遇到了问题:

  

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

这是我到目前为止的代码:

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

我正在构建Controller:

    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 {}

问题

提供View的路径,如何从Test项目将其呈现为HTML?或者更具体地说,我如何模拟ControllerContext.DisplayMode

2 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:0)

您需要一个空的Controller仅用于测试(ex 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();
    }
}