如何在Visual Studio单元测试框架中将视图呈现为字符串?

如果我创建一个新的MVC 5项目(使用单元测试)并为我的控制器创建一个新的超类,使用来自流行的SO答案的片段,很容易将视图的内容呈现为字符串:

HomeController.cs

public class HomeController : StringableController
{
    public ActionResult StringIndex()
    {
        string result = RenderRazorViewToString("Index", null);

        return Content(result);
    }
}

现在,如果我访问/Home/StringIndex ,我会返回该视图的原始HTML。 整洁(即使不是很有用)! 但在.Tests项目中,如果我尝试在单元测试中测试StringIndex()...

HomeControllerTest.cs

[TestClass]
public class HomeControllerTest
{
    [TestMethod]
    public void StringIndex()
    {
        HomeController controller = new HomeController();

        ContentResult result = controller.StringIndex() as ContentResult;
        string resultString = result.Content;

        Assert.IsTrue(resultString.Contains("Getting started"));
    }
}

没有这样的运气。 由于controllerContextnull ,因此在上述代码段中调用System.Web.Mvc.ViewEngineCollection.FindPartialView()时,从单元测试调用controller.StringIndex()会创建一个ArgumentNullException 。 我已经尝试了一些基于Moq的方法( SetUpForTest()MvcMockHelpers修改版本)来模拟controllerContext ,但这可能是错误的方法,因为1)这两种方法都不是专门为Visual Studio中的单元测试量身定做的,2)我不完全确定为了成功呈现视图,需要真实与嘲笑。

是否可以在Visual Studio单元测试中创建一个能够使RenderRazorViewToString()正常工作的controllerContext?

编辑来澄清我的目标:我不想测试RenderRazorViewToString()的内部工作(它只是一个用于工作的工具); 我希望我的单元测试能够分析在正常情况下从控制器返回的实际HTML。 因此,如果(作为一个糟糕的,愚蠢的例子)我的Index.cshtml只是<h2>@DateTime.Now.Year</h2> ,那么Assert.IsTrue(resultString.Contains("<h2>2013</h2> ")); (如HomeControllerTest.StringIndex()的最后一行)将会成功。


你可以用这个方法来测试一下,只需要很少的调整。 为了测试这个,你需要修改你的SUT(被测系统),这样它就变得更加可测试了。 改变你的SUT总是一件好事,所以API变得更加可测试,即使有时它看起来有点奇怪。

你的SUT中有很多元凶很难测试。 一个。

        using (var sw = new StringWriter())

湾 (在RenderRazorViewToString中)

        ViewEngines.Engines.FindPartialView(ControllerContext, "Index");

使用StringWriter,你需要能够掌握一个由StringWriter开始的测试,这样你就可以控制那个编写者已经写到View的内容了。

使用FindPartialView,ViewEnginesCollection是ViewEngines中的一个静态集合,FindPartialView有很多事情发生在底下,而且看起来更难以断开。 由于FindPartialView是虚拟的,因此可能有另一种方法,所以我们可以注入一个桩状的ViewEngine,我们可以存留FindPartialView方法。 但是我并没有对整个宇宙进行存根/模拟,所以我采取了不同的方法,但仍然达到了目的。 这是通过引入一个委托,所以我完全控制了FindPartialView返回的内容。

被测系统(SUT)

public class HomeController : Controller
{
    public Func<ViewEngineResult> ViewEngineResultFunc { get; set; }
    public Func<StringWriter> StringWriterFunc { get; set; }

    public HomeController()
    {
        ViewEngineResultFunc = () =>
        ViewEngines.Engines.FindPartialView(ControllerContext, "Index");
    }

    private string RenderRazorViewToString(string viewName, object model)
    {
        ViewData.Model = model;
        using (var sw = new StringWriter())
        {
            StringWriter stringWriter = StringWriterFunc == null ?
                            sw : StringWriterFunc();

            var viewResult = ViewEngineResultFunc();
            var viewContext = new ViewContext(ControllerContext, 
                viewResult.View, ViewData, TempData, stringWriter);
            viewResult.View.Render(viewContext, stringWriter);
            viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
            return stringWriter.GetStringBuilder().ToString();
        }
    }

    public ActionResult StringIndex()
    {
        string result = RenderRazorViewToString("Index", null);
        return Content(result);
    }

正如你所看到的,StringWriter有两个委托,由StringWriterFunc调用,另一个由ViewEngineResultFunc调用的FindPartialView。

在实际的程序执行过程中,这些代表应该使用真实的实例,其中在测试执行期间将由假实例代替。

单元测试

[TestClass]
public class HomeControllerTest
{
    [TestMethod]
    public void StringIndex_RenderViewToString_ContentResuleContainsExpectedString()
    {
        //Arrange
        const string viewHtmlContent = "expectedViewContext";
        var sut = new HomeController();
        var sw = new StringWriter();
        var viewEngineResult = SetupViewContent(viewHtmlContent, sw);
        var controllerContext = new ControllerContext
          (new Mock<HttpContextBase>().Object, new RouteData(), 
          new Mock<ControllerBase>().Object);
        sut.ControllerContext = controllerContext;
        sut.ViewEngineResultFunc = () => viewEngineResult;
        sut.StringWriterFunc = () => sw;

        //Act
        var result = sut.StringIndex() as ContentResult;
        string resultString = result.Content;

        //Assert
        Assert.IsTrue(resultString.Contains(viewHtmlContent));
    }

    private static ViewEngineResult 
       SetupViewContent(string viewHtmlContent, StringWriter stringWriter)
    {
        var mockedViewEngine = new Mock<IViewEngine>();
        var resultView = new Mock<IView>();

        resultView.Setup(x => x.Render(It.IsAny<ViewContext>(), 
          It.IsAny<StringWriter>()))
              .Callback(() => stringWriter.Write(viewHtmlContent));
        var viewEngineResult = new ViewEngineResult
                (resultView.Object, mockedViewEngine.Object);

        ViewEngines.Engines.Clear();
        ViewEngines.Engines.Add(mockedViewEngine.Object);
        return viewEngineResult;
    }
}

直接分析HTML的另一种方法是使用测试框架,例如Selenium WebDriver(来自编码单元测试),它将以编程方式“驱动”待测页面,然后您可以使用WebDriver将测试断言写入“页面”检查是否存在元素,元素值等

这里有一个很好的使用MVC和IIS Express的单元测试的例子

链接地址: http://www.djcxy.com/p/18099.html

上一篇: How can I render a view into a string in Visual Studio Unit Testing Framework?

下一篇: Is it possible to draw an image in the console using C#?