如果我创建一个新的MVC 5项目(使用单元测试)并使用来自流行的SO答案的snippet为我的控制器创建一个新的超类,则很容易将视图的内容呈现为字符串:
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"));
}
}
......没有这样的运气。由于controller.StringIndex()
为ArgumentNullException
,在上述snippet中调用System.Web.Mvc.ViewEngineCollection.FindPartialView()
时,从单元测试中调用controllerContext
会产生null
。我尝试了一些基于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()
中的最后一行)将会成功。
答案 0 :(得分:2)
您可以通过一些调整来测试此方法。为了测试这一点,您需要修改您的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;
}
}
答案 1 :(得分:1)
直接分析HTML的替代方法是使用测试框架,例如Selenium WebDriver(来自编码单元测试),它将以编程方式“驱动”正在测试的页面,然后您可以编写使用WebDriver检查元素,元素值等是否存在对“页面”的测试断言。
在使用MVC和IIS Express进行单元测试时,有一个很好的写法here