Home > .net 3.5, Microsoft, MVC, Visual Studio 2008 > The .NET MVC Framework – Part 2 – Testing Complex Routes and Controllers

The .NET MVC Framework – Part 2 – Testing Complex Routes and Controllers

December 18, 2007

This is part two of the series looking at the new .NET MVC framework.  In the last post, I discussed a bit of background of the MVC framework, setting up routes, and creating the first new controller and view for the project.

Before I begin, I want to point out that I’m using Rhino Mocks 3.3.  Works great so far—if you run into any issues with this code, please post up and let me know.

Testing Routes

Phil Haack, the Senior Program Manager for the ASP.NET team, has created a great blog post (and attached helper methods) for testing routes using Rhino Mocks.  I highly suggest reading his post before proceeding.

I’m using his “MvcMockHelpers” and “TestHelper” classes—they’re fantastic and, maybe if we ask nicely, might find their way into the MSTest or Mvc framework itself.

After those two files are added into your project, add a new Unit Test template to your project.  I’ll call mine, similar to Phil’s, RouteTests (I may not lose it or forget what it does if it’s called that. ;)).

Using the AssertRoute of Phil’s TestHelper class, we can quickly and easily see if the RouteTable we’ve specified is working.

[TestMethod]

public void CanMapNormalControllerAndDefaultActionRoute()

{

RouteCollection routes = new RouteCollection();

       RouteManager.RegisterRoutes(routes);

 

       TestHelper.AssertRoute(

routes,

“home”,

new { controller = “home”, action = “index” });

}

AssertRoute, when used like this, has three parameters.

“routes” – passes the RouteCollection generated from our RouteManager—we could explicitly define additional routes if we wanted and inject them here.

“home” – the controller/view we want to render.  It could just as easily say “galleries/view/12”.

new {} anonymous type – this is the expected RETURN of the assert; for this example, by using the specified route and calling “home”, we expect the MVC application to return the “home” controller and respond with the “index” action.

I’ve also taken the RouteCollection and RouteManager and pulled those two lines of code out into a “BuildDefaultRoutes” method.  I can call that method when needed OR skip it and build my own when needed.

private RouteCollection BuildDefaultRoutes()

{

RouteCollection defaultRoutes = new RouteCollection();

RouteManager.RegisterRoutes(defaultRoutes);

return defaultRoutes;

}

Now, what about our Galleries/Show route, we want to verify that it’s a valid path.

[TestMethod]

public void CanMap_Galleries()

{

TestHelper.AssertRoute(BuildDefaultRoutes(), “galleries/show”,

new { controller = “galleries”, action = “show” });

}

Galleries test #1 - Passed!

Good deal, “galleries/show” will route to the controller and action I expect.

Now, what if I want to test something a bit more unique—I want to be able to handle the CURRENT urls that are being passed to the WebStorage gallery at http://photos.tiredstudent.com.

~/WebStorageHandler.ashx?id=166&tb=false&type=Photo

The query string contains three important parts of information as we progress—the ID of the object in the database, whether or not to generate a thumbnail, and what type of object it is (so it knows how to handle the stream—something that needs fixed).

So, we can use our RoutesTest to build our test, have it fail, and then build the right route to make the test pass.

[TestMethod]

public void CanMap_OldWebStorageHandler()

{          

       // Develop our test using our new route.

       TestHelper.AssertRoute(

BuildDefaultRoutes(),

“WebStorageHandler.ashx?id=166&tb=false&type=Photo”,

new {

controller = “galleries”,

action = “CatchHandler” });

}

In this test, we’re looking for that specific URL, and want it to forward it to the Show action on the Galleries controller and pass along the ID of 166 (since, by default, the URL routes read the query string if the parameters can’t be found in the path).

Now that our test is in there (and fails), what kind of route and controller action would we need to add?

routes.Add(new Route

{

       Url = “WebStorageHandler.ashx”,

Defaults = new

       {

controller = “galleries”,

             action = “CatchHandler”

},

RouteHandler = typeof(MvcRouteHandler)

});

 

[ControllerAction]

public void CatchHandler(int id, bool? tb)

{

if (tb == true)

{

             RedirectToAction(new

             {

             action = “ShowThumbnail”,

             id = id

             });

}

       else

       {

             RedirectToAction(new

             {

             action = “Show”,

             id = id

             });

}

}

We can test the route, controller, and actions by prefabing two real URLs taken from the live Photo site:

WebStorageHandler.ashx?id=166&tb=false&type=Photo successfully redirects to /galleries/Show/166

and

WebStorageHandler.ashx?id=166&tb=true&type=Photo successfully redirects to /galleries/ShowThumbnail/166

Good deal!

WarningNotice: I’ll be posting a follow-up later today that describes a current issue with mocking up these sorts of complex routes.  If you attempt to pass query string parameters as your expectations, and your parameters are not EXACT within your route information, the test will fail.  The follow-up will describe how to pull in the query string parameters.

This route intercepts anything looking for WebStorageHandler.ashx and forwards it on to the Galleries controller/CatchHandler action—passing along the rest as parameters.

Testing Controllers

Writing accurate Controller tests also has it’s complications unique challenges with the MVC Framework.  Phil has posted up his blog that the current breakage in Mocks should be fixed soon; however, the subclass techniques seem to be gaining popularity [ David Hayden’s post is interesting and has a good debate of comments on it as well]. 

To create the subclasses, create a new class and inherit from the base controller.

Here’s an example using the GalleriesController.

public class GalleriesControllerTester : GalleriesController

{

public string ActualViewName;

       public string ActualMasterName;

       public object ActualViewData;

       public string RedirectToActionValues;

 

protected override void RenderView(string viewName,

string masterName, object viewData)

        {

            ActualViewName = viewName;

            ActualMasterName = masterName;

            ActualViewData = viewData;

        }

 

        protected override void RedirectToAction(object values)

        {

            RedirectToActionValues = values.ToString();

        }

}

InformationFor now, and until later CTPs that allow me to mock these up (or at least use an interface/base class), I’m placing these in a separate class called ControllerTesters.  That’s not necessary (I noticed David and Phil both placing theirs directly inside the {x}ControllerTest classes).  So, you’ll need a {x}ControllerTester for every controller.😦

[TestMethod]

public void CanViewGalleries_Show()

{

GalleriesControllerTester controller = new GalleriesControllerTester();

       controller.Show(1);

       Assert.AreEqual(“Show”, controller.ActualViewName);

}

So what does this tell us?

Using the Tester subclass, we can assert whether or not the .Show() action and resulting view match what we expect.

Conclusion

This covers creating unit tests for your routes, and your controllers/views; you might be wondering why I don’t have any testing of the models.  Since unit testing LINQ-to-SQL isn’t specific to MVC, I’ll leave that out for now—there’s plenty of information on that out there.

 

%d bloggers like this: