单元测试或运行时使用ExecuteResult返回ActionResult字符串输出响应?

5

问题...

如何最好地单元测试多个控制器方法的字符串响应内容类型

使用...

每个方法都返回一个ActionResult,其中一些是ViewResult响应。我正在使用ASP.NET MVC 2 RTM和Moq

具体来说...

我希望从HttpContext.Response中获取TextWriter,并让它包含ActionResult的完整字符串响应。

为什么?

1. 单元测试中

我想测试输出中是否存在某些特定的内容。

2. 运行时通过工作线程

我使用后台工作线程更新远程服务器上的静态内容,这些内容是控制器的输出,并且必须按此生成。通过HTTP向同一服务器发出请求是不可取的,因为有许多1000个要更新的文件。

我看到相同的代码在运行时和通过单元测试使用,因为它们非常相似?

难点1

如何正确设置模拟以不需要路由调用RegisterRoutesRegisterAllAreas就能使调用成功,目前会在BuildManagerWrapper::IBuildManager.GetReferencedAssemblies中抛出异常。

示例代码

我的模拟辅助程序如下:

public static HttpContextBase FakeHttpContext()
{
    var context = new Mock<HttpContextBase>();
    var request = new Mock<HttpRequestBase>();
    var response = new Mock<HttpResponseBase>();
    var session = new Mock<HttpSessionStateBase>();
    var server = new Mock<HttpServerUtilityBase>();
    var writer = new StringWriter();

    var form = new NameValueCollection();
    var queryString = new NameValueCollection();
    request.Setup(r => r.Form).Returns(form);
    request.Setup(r => r.QueryString).Returns(queryString);

    context.Setup(ctx => ctx.Request).Returns(request.Object);
    context.Setup(ctx => ctx.Response).Returns(response.Object);
    context.Setup(ctx => ctx.Session).Returns(session.Object);
    context.Setup(ctx => ctx.Server).Returns(server.Object);
    context.Setup(ctx => ctx.Response.Output).Returns(writer);

    return context.Object;
}

public static void SetFakeControllerContext(this Controller controller)
{
    var httpContext = FakeHttpContext();
    var routeData = new RouteData();
    var routeData = RouteTable.Routes.GetRouteData(httpContext);
    ControllerContext context = new ControllerContext(new RequestContext(httpContext, routeData), controller);
    controller.ControllerContext = context;
}

我的当前测试方法尝试如下:

[TestMethod]
public void CodedJavaScriptAction_Should_Return_JavaScript_Response()
{
    // Arrange
    var controller = new CodedController();
    controller.SetFakeControllerContext();

    // Act
    var result = controller.CodedJavaScript(); // Response is made up as a ViewResult containing JavaScript.
    var controllerContext = controller.ControllerContext;
    var routeData = controllerContext.RouteData;
    routeData.DataTokens.Add("area", "Coded");
    routeData.Values.Add("area", "Coded");
    routeData.Values.Add("controller", "Coded");
    routeData.Values.Add("action", "CodedJavaScript");

    var response = controllerContext.HttpContext.Response;
    response.Buffer = true;
    var vr = result as ViewResult;
    vr.MasterName = "CodedJavaScript";

    result.ExecuteResult(controllerContext);

    // Assert
    var s = response.Output.ToString();
    Assert.AreEqual("text/javascript", response.ContentType);
    Assert.IsTrue(s.Length > 0);
    // @todo: Further tests to be added here.   

}

我的区域、视图和共享文件如下:

-Areas\Coded\Controllers\CodeController.cs
-Areas\Coded\Views\Coded\CodedJavaScript.aspx
-Areas\Coded\CodedAreaRegistration.cs
-Views\Shared\CodedJavaScript.Master

编辑:现在已经包括单元测试和运行时执行。感谢@Darin Dimitrov提到的集成测试,但是现在这个问题还有一个运行时元素。

编辑:经过一些测试和审查,使用alexn引用的MvcIntegrationTestFramework的代码,该代码使用AppDomain.CreateDomainSimpleWorkerRequest创建新请求,我发现在已经有活动请求的进程中无法通过此方法创建新请求,因为使用了static值。因此,这种方法被排除。

可能是同样的问题,但我现在想知道是否可以更直接地将部分视图的结果作为字符串返回?


字符串响应是由视图引擎在执行管道中比控制器操作晚得多时执行的。你到底想在这里测试什么:你的控制器还是你的视图?因为如果是你的控制器,测试生成的字符串就毫无意义了。只需测试该操作返回正确的ActionResult类型即可。 - Darin Dimitrov
@Darin Dimtrov感谢您的评论。我特别想获取ActionResult类型的文本内容。不幸的是,我想要同时测试控制器和视图,因为查询字符串参数可以在控制器选择的数据和视图输出方面创建一系列变化的组合。在这种情况下单独测试它们将不是真正的测试。 - Dean Taylor
2个回答

3
您所要实现的不再是单元测试,而是集成测试,因为您不再独立地测试应用程序组件,并且有很好的工具可以帮助您进行此操作,如Selenium或Visual Studio Ultimate版本中的Web Tests
这些测试的优点在于它们模拟用户请求并覆盖整个应用程序行为。因此,对于给定的用户请求,您可以断言您的应用程序是否正确响应。思路是编写用户场景、记录它们,然后自动化执行这些测试以断言您的应用程序是否按照规定响应。

非常感谢您的回答。我已经更新了问题,包括通过工作线程运行而不仅仅是通过单元测试运行。非常感谢您的回答,如果只是针对单元测试,那么它是非常有价值的 - 那么关于运行时方面的想法呢? - Dean Taylor
@Dean,我不太确定理解这个运行时方面。所以你是说你的控制器操作基本上生成JavaScript,你正在尝试测试此操作的输出?给定某些用户输入生成的确切JavaScript?你所说的后台工作线程是什么?这是您要测试的应用程序的一部分还是其他应用程序调用MVC应用程序的一部分?最后,您能否提供一个您要测试的操作示例? - Darin Dimitrov
是的,这些操作会生成JavaScript、XML、JSON和纯文本,我希望在测试输出时进行测试。但是MVC应用程序中有一个后台工作线程,当特定的DB值发生更改时运行,基于一个脏列表,工作线程会引用它。工作线程查找这些脏项,并将JavaScript、JSON、XML等通过FTP和HTTP POST推送到另一个位置。 - Dean Taylor
Selenium和Web测试解决方案需要已登录的用户和未锁定的会话,对吗?如果是这样,还有其他什么可以解决这个问题吗? - Maslow

2
我使用 Steven Sanderson 的 MvcIntegrationTestFramework 取得了很好的效果。它非常易于使用。
通过使用此工具,你可以轻松地测试输出响应、视图数据、Cookies、Session 等等,而只需付出很少的努力。
你可以通过类似下面的测试来测试呈现的 HTML:
[Test]
public void Output_Contains_String()
{
    appHost.SimulateBrowsingSession(session => {
        var result = session.ProcessRequest("/");
        Assert.IsTrue(result.ResponseText.Contains("string to check for"));
    });
}

没有嘲笑和路由注册。非常干净。
由于这是技术上的集成测试,设置和运行需要一些时间。
如果您需要更多示例或更多信息,请告诉我。

查看MVCintegrationTestFramework的源代码似乎是一个有趣的方法。但对于我的项目的运行时(非测试)方面来说,为每个请求设置一个AppInstance等似乎是很昂贵的。 - Dean Taylor
我不太明白你所说的运行时方面是什么意思。你真正想做的是集成测试(测试所有组件)。你不应该在生产环境中使用此代码或处理实际请求。生成的视图是否是带有布局等完整网页?如果只是显示简单文本,也许你可以将输出数据设置在ViewData.Model中,并生成XmlResult或JsonResult或任何你需要显示的内容,并从ViewData.Model中读取它们? - alexn
不幸的是,输出格式有多种类型,包括完整的 HTML 页面、JavaScript、JSON、XML 等等。 我更倾向于使用 MVC 视图系统,而不是把输出强制放到不应该放置的地方。谢谢。 - Dean Taylor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接