Question

The Question...

What is the best way to Unit Test the string response and content type from several Controller methods?

Using...

Each method returns an ActionResult, some of which are ViewResult responses. I'm using ASP.NET MVC 2 RTM and Moq.

Specifically...

I wish to obtain the TextWriter from HttpContext.Response and have it contain the full string response from an ActionResult.

Why?

1. Within Unit Tests

I want to test some specific if content does and does not exist with the output.

2. Runtime via worker thread

I use a background worker thread to update static content on remote servers, this content is the output from the Controllers and must be generated as such. Making requests to the same server via HTTP is not advisable because there are many 1000's of files which are updated.

I see the same code being used at both Runtime and via Unit Tests, as it would be very similar?

Stumbling block 1

How to correctly setup mocking to not require Routes Or call RegisterRoutes and RegisterAllAreas have the call succeed, currently throws an exception deep inside BuildManagerWrapper::IBuildManager.GetReferencedAssemblies.

Sample Code

My mocking helpers look like this:

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

And my current attempt at a TestMethod is as follows:

[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.   

}

My area, views and shared files are:

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

EDIT: edited to now include both Unit Testing and Runtime execution. Thanks to @Darin Dimitrov for mentioning Integration Testing but there is now also a runtime element to this question.

EDIT: After some testing and review using some of the source code from MvcIntegrationTestFramework as referenced by alexn. Which uses AppDomain.CreateDomain and SimpleWorkerRequest to create a new request, I have found that it is not possible to create a new request via this method in a process that already has an active request, due to static values used. So this rules this method out.

Probably the same issue but I am now wondering if the result from a Partial View can be returned as a string more directly?

Was it helpful?

Solution

What you are trying to achieve is no longer unit tests but integration testing because you are no longer testing your application components in isolation and there are great tools that allow you to do this like Selenium or Web Tests in the Ultimate versions of Visual Studio.

The advantage of these tests is that they simulate user requests and cover the entire application behavior. So for a given user request you can assert that your application responds properly. The idea is that you write user scenarios, record them and then you could automate the execution of those tests to assert that your application responds as specified.

OTHER TIPS

I use Steven Sandersons MvcIntegrationTestFramework with great success. It's very easy to use.

With this, you can easily test output response, viewdata, cookies, session and a lot more with very little effort.

You would test the rendered HTML with a test looking something like this:

[Test]
public void Output_Contains_String()
{
    appHost.SimulateBrowsingSession(session => {
        var result = session.ProcessRequest("/");
        Assert.IsTrue(result.ResponseText.Contains("string to check for"));
    });
}

No mocking and routes registering. Very clean.

As this is technically an integration test, it will take some time to set up and run.

Please let me know if you need any more examples or some more information.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top