About this Project

Have you ever gotten your MVC application's routes working exactly the way you want -- only to add a new pattern in your Global.asax that screws everything up? This used to happen to me all the time; often without realizing it until well after the fact. Out of that frustration, MVC Route/URL Generation Unit Tester was born.

MVC Route/URL Generation Unit Tester provides convenient, easy to use methods that let you unit test the route table in your ASP.NET MVC application. Unlike many libraries, this lets you test routes both ways -- both incoming and outgoing. You can specify an incoming request and make one of several assertions about the outcome; such that the request matches a given route, matches no routes, or that it is ignored by the routing system. You can also specify route data and assert that a given URL will be generated by your application.

MVC Route/URL Generation Unit Tester works with any unit testing library you choose, such as MSTest, NUnit, xUnit, MbUnit, and others.

Important Changes

Be sure to check frequently for new feature updates while the library remains in beta. Any significant updates will be noted on the Change Log page. You can also follow me @leedumond on Twitter for any important change notifications.

Step 1: Install the Library via Nuget

From within your unit test project, PM> Install-Package MvcRouteUnitTester or via the Nuget management tools in Visual Studio.

Step 2: Test Your Incoming Routes

Here, you will set up a test for your incoming routes.

using MvcRouteUnitTester;

[TestClass]
public class RouteTests
{
   [TestMethod]
   public void TestIncomingRoutes()
   {
      // Arrange
      var tester = new RouteTester<MvcApplication>();

      // Assert
      tester.WithIncomingRequest("/").ShouldMatchRoute("Home", "Index");
      tester.WithIncomingRequest("/Foo").ShouldMatchRoute("Foo", "Index");
      tester.WithIncomingRequest("/Foo/Index").ShouldMatchRoute("Foo", "Index");
      tester.WithIncomingRequest("/Foo/Bar").ShouldMatchRoute("Foo", "Bar");
      tester.WithIncomingRequest("/Foo/Bar/5").ShouldMatchRoute("Foo", "Bar", new { id = 5 });

      tester.WithIncomingRequest("/Foo/Bar/5/Baz").ShouldMatchNoRoute();

      tester.WithIncomingRequest("/handler.axd/pathInfo").ShouldBeIgnored();
   }
}
First, you arrange the test by instantiating a new RouteTester that you can use to test your routes. You do that by invoking the generic constructor and passing in the name of your HttpApplication class in Global.asax (when you create a new application, this class is named MvcApplication by default).

After you've done that, you can specify the incoming request you want to test by passing the request URL to the WithIncomingRequest method. You then assert using the ShouldMatchRoute method, passing in the names of the action and controller you expect the given request to match. You will also pass in the URL parameter values from the request, as well as the default values from the route you expect to match, using an anonymous object. If the assertion fails, the ShouldMatchRoute method throws an AssertionException, whose message will reveal the reason the failure occurred.

If you want to confirm that a given request will NOT be matched by any route, you can call the ShouldMatchNoRoute method (this is handy when testing constrained routes). If you want to confirm that a request is ignored by the routing system, you can call the ShouldBeIgnored method. (Note that these are two different conditions, as an ignored route still counts as a match).

NOTE: The WithIncomingRequest method also accepts an optional string parameter that allows you to specify the HTTP method of the request (with a default of "GET".) This is handy for testing routes constrained using an HttpMethodConstraint. Note that this is not the same as restricting action methods using HttpGetAttribute or HttpPostAttribute. Remember: this library tests your routes, not your action methods. :-)

Step 3: Test Your Outgoing Routes (URL Generation)

Then, you can set up a test for your outgoing routes.

using MvcRouteUnitTester;

[TestClass]
public class RouteTests
{
   [TestMethod]
   public void TestIncomingRoutes()
   {
      // see Step 2 above...
   }
   
   [TestMethod]
   public void TestOutgoingRoutes()
   {
      // Arrange
      var tester = new RouteTester<MvcApplication>();

      // Assert
      tester.WithRouteInfo("Home", "Index").ShouldGenerateUrl("/");
      tester.WithRouteInfo("Home", "About").ShouldGenerateUrl("/Home/About");
      tester.WithRouteInfo("Home", "About", new { id = 5 }).ShouldGenerateUrl("/Home/About/5");
      tester.WithRouteInfo("Home", "About", new { id = 5, someKey = "someValue" }).ShouldGenerateUrl("/Home/About/5?someKey=someValue");
   }
}
As before, you arrange the test by instantiating a RouteTester to work with.

You specify the routing data you want to test against by passing the name of the action, the controller, and any additional route values to the WithRouteInfo method. You then assert using the ShouldGenerateUrl method, passing in the URL you expect your application to generate with the given information. If the assertion fails, the ShouldGenerateUrl method throws an AssertionException that reveals the reason the failure occurred.

IMPORTANT: The ShouldGenerateUrl method is case-sensitive. Make sure the string you pass to the ShouldGenerateUrl method is cased as the application provides, otherwise the test will fail. For example, if you're using this in conjunction with my LowercaseRoutesMVC utility, you'd want to pass in lower case URLs, like .ShouldGenerateUrl("/home/about").

Step 4: Testing Area Routes

You may have noticed that the ShouldMatchRoute and WithRouteInfo methods have overloads that let you supply an area argument as a string. Those are the methods to use when you want to test your Area routes.

For the purposes of this discussion, we will assume you have added an area called "Admin" to your application. When you did that, a folder named Areas was added to your application, and inside that folder another folder named Admin. Inside that Admin folder, there is a class called AdminAreaRegistration in which you would define the routes for your Admin area.

Here is what the route tests for your Admin area might look like:

using MvcRouteUnitTester;

[TestClass]
public class AdminAreaRouteTests
{
   [TestMethod]
   public void TestIncomingRoutes()
   {
      // Arrange
      var tester = new RouteTester<AdminAreaRegistration>();

      // Assert
      tester.WithIncomingRequest("/Admin/Foo").ShouldMatchRoute("Admin", "Foo", "Index");
      tester.WithIncomingRequest("/Admin/Foo/Index").ShouldMatchRoute("Admin", "Foo", "Index");
      tester.WithIncomingRequest("/Admin/Foo/Bar").ShouldMatchRoute("Admin", "Foo", "Bar");
      tester.WithIncomingRequest("/Admin/Foo/Bar/5").ShouldMatchRoute("Admin", "Foo", "Bar", new { id = 5 });

      tester.WithIncomingRequest("/Admin").ShouldMatchNoRoute();
      tester.WithIncomingRequest("/Admin/Foo/Bar/5/Baz").ShouldMatchNoRoute();
   }

   [TestMethod]
   public void TestOutgoingRoutes()
   {
      // Arrange
      var tester = new RouteTester<AdminAreaRegistration>();

      // Assert
      tester.WithRouteInfo("Admin", "Foo", "Bar").ShouldGenerateUrl("/Admin/Foo/Bar");
      tester.WithRouteInfo("Admin", "Foo", "Bar", new { id = 5 }).ShouldGenerateUrl("/Admin/Foo/Bar/5");
   }
}
This time, you instantiate the RouteTester by passing in the class that contains your Area route definitions. The testing procedure is pretty much the same -- except of course, you will be using the overloads of ShouldMatchRoute and WithRouteInfo that take the area string argument.

Customizing the HTTP context used for tests

The WithRouteInfo and WithRequestInfo methods use an HTTP context which is mocked up internally for the purposes of performing the test. For the vast majority of cases, this is something you don't need to be concerned about. However, there may be some specialized cases where you need to alter or customize the context (For an example, see Additional setup in TestUtility.GetHttpContext(..)).

For these special use cases, the RouteInfo and RequestInfo classes expose an HttpContext property which allows you to obtain the underlying context. From there, you can extend the context as you desire, or even obtain the underlying mock of it (using Mock.Get()) to create additional setups.

Questions? Issues? Suggestions?

Feel free to post them under the Discussions tab above.

Last edited Apr 27, 2012 at 8:22 PM by LDumond, version 81