Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test or runtime ActionResult string output response via ExecuteResult?

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?

like image 456
Dean Taylor Avatar asked Nov 18 '10 15:11

Dean Taylor


People also ask

Can I return action result instead of view result?

When you set Action's return type ActionResult , you can return any subtype of it e.g Json,PartialView,View,RedirectToAction.

Should controllers be unit tested?

If you've writing custom filters, routes, etc, you should unit test them, but not as part of your tests on a particular controller action. They should be tested in isolation.

How do you write a controller unit test?

Writing a Unit Test for REST Controller First, we need to create Abstract class file used to create web application context by using MockMvc and define the mapToJson() and mapFromJson() methods to convert the Java object into JSON string and convert the JSON string into Java object.


2 Answers

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.

like image 99
Darin Dimitrov Avatar answered Oct 19 '22 22:10

Darin Dimitrov


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.

like image 2
alexn Avatar answered Oct 19 '22 22:10

alexn