RouteEvaluator For Unit Testing Routes

A while back I wrote a routing debugger which is useful for testing your routes and seeing which routes would match a given URL. Rob suggested we have something like this for unit tests, so I whipped something simple up.

This is a class that allows you to test multiple different URLs quickly. You simply create the RouteEvaluator giving it a collection of routes and then GetMatches which returns a List<RouteData> containing a RouteData instance for every route that matches, not just the first one.

Here's a sample of usage.

[Test]
public void CanMatchUsingRouteEvaluator()
{
  var routes = new RouteCollection();
  GlobalApplication.RegisterRoutes(routes);

  var evaluator = new RouteEvaluator(routes);
  var matchingRouteData = evaluator.GetMatches("~/foo/bar");
  Assert.IsTrue(matchingRouteData.Count > 0);
  matchingRouteData = evaluator.GetMatches("~/foo/bar/baz/billy");
  Assert.AreEqual(0, matchingRouteData.Count);
}

And here’s the code. Note that my implementation relies on Moq, but you could easily implement it without using Moq if you wanted to.

public class RouteEvaluator
{
  RouteCollection routes;
    
  public RouteEvaluator(RouteCollection routes)
  {
    this.routes = routes;
  }

  public IList<RouteData> GetMatches(string virtualPath)
  {
    return GetMatches(virtualPath, "GET");
  }

  public IList<RouteData> GetMatches(string virtualPath, string httpMethod)
  {
    List<RouteData> matchingRouteData = new List<RouteData>();

    foreach (var route in this.routes)
    {
      var context = new Mock<HttpContextBase>();
      var request = new Mock<HttpRequestBase>();

      context.Expect(ctx => ctx.Request).Returns(request.Object);
      request.Expect(req => req.PathInfo).Returns(string.Empty);
      request.Expect(req => 
req.AppRelativeCurrentExecutionFilePath).Returns(virtualPath);
      if (!string.IsNullOrEmpty(httpMethod))
      {
        request.Expect(req => req.HttpMethod).Returns(httpMethod);
      }

      RouteData routeData = this.routes.GetRouteData(context.Object);
      if (routeData != null) {
        matchingRouteData.Add(routeData);
      }
    }
    return matchingRouteData;
  }
}

Let me know if this ends up being useful to you.

Technorati Tags: ,,

What others have said

Requesting Gravatar... Rob Conery May 05, 2008 5:45 PM
# re: RouteEvaluator For Unit Testing Routes
OMFG I love you!
Requesting Gravatar... Vijay Santhanam May 05, 2008 7:27 PM
# re: RouteEvaluator For Unit Testing Routes
nice! you're a Moq convert aren't you? me too.
Requesting Gravatar... Simone Busoli May 06, 2008 12:51 AM
# re: RouteEvaluator For Unit Testing Routes
Very cool Phil, that's something I didn't think about, but looks very useful.
Requesting Gravatar... Pete May 06, 2008 4:49 AM
# re: RouteEvaluator For Unit Testing Routes
What the heck was that "GlobalApplication"?! Design-disaster and naming disaster.

I've read somewhere "no global objects in OO". And the name GlobalApplication doesn't really describe anything. What is a NonGlobalApplication? Or LocalApplication.

Crazy stuff. Otherwise it's all good.
Requesting Gravatar... Simone Busoli May 06, 2008 5:08 AM
# re: RouteEvaluator For Unit Testing Routes
Pete, it's just the name of the class behind Global.asax, nothing weird ;)
Requesting Gravatar... Troy DeMonbreun May 06, 2008 10:04 AM
# re: RouteEvaluator For Unit Testing Routes
I'm presuming you guys wouldn't literally write tests that had a proliferation of URLs to verify?

I.e., that the above unit test is just a simplified example and it would be better practice to write a much less brittle unit test, e.g.: reference an external file containing the URLs (and possibly other metadata) and write the test to iterate the file and output a global pass/fail (with maybe summary text of the URLs that failed)?

Requesting Gravatar... tgmdbm May 06, 2008 8:23 PM
# re: RouteEvaluator For Unit Testing Routes
Love it. Nice one Phil.

@Troy, yeah, I think using a csv file with url,routeName,failureMessage is a nice idea. Then you can write one test to rule them all. But this is really down to your own preference, and might not be applicable in certain projects.

My only problem with testing urls is I don't really care what my urls look like, read: "I have no *good* reason to care". As long as when i call ActionLink passing in whatever values, it generates a url such that requesting that url will pass those values to my controller action. If that is true then I'm happy.

So what I do is exactly that. I get the url from GetVirtualPath, and pass it back through GetRouteData and Assert that what i get out is what i put in. I don't test the url directly.
Requesting Gravatar... Steve May 07, 2008 2:36 AM
# re: RouteEvaluator For Unit Testing Routes
Has anyone managed to get the code to work correctly?

Besides the fact that it doesn't compile (Calls to GetMatchingRoutes should be changed to GetMatches, or vice-versa), it always returns as many routes as I have registered in my RouteCollection, and they're always excatly the same routes! It looks like the foreach in GetMatches is redundant, but then I can't see how it can find multiple routes that match the same rule?

Also, is there any way to find out whether the Controller returned for a route exists?
Requesting Gravatar... Haacked May 07, 2008 8:08 AM
# re: RouteEvaluator For Unit Testing Routes
@Steve sorry, i had some typos in there. I fixed them up.
Requesting Gravatar... Guy Jun 06, 2008 11:27 AM
# re: RouteEvaluator For Unit Testing Routes
Thank You, every cool. The value added to an already great product just makes it that much more fantastic. I was affraid there would be no support for MVC, keep it up.

A few points that need to be made clear for this to work. In the flurry of anxiety to get this to the web I think a "None Informed Use Case" was not done. I don't think a dry run of implmenting this was done. If one just takes what is give it does not work. No instructions on how to set it up. It was just assumed that we all know about [test] and that means NUnit (instead [TestMethod]) and we all know that GlobalApplication is the class name for Global.asx.cs which in some cases it is not. It may just be Global. Or as one persone points out using "Global" is bad and changed it to something completely different.

Here are the preperation steps I think are missing.
1. Make sure in the TestProject you make has a reference to your MVC Web Project (Company.Foo.WebApplication).
2. Also make a reference to System.Web
3. I could not use GlobalApplication.RegisterRoutes(routes); I had to do the following:

Company.Foo.WebApplication.Global webApp = new Company.Foo.WebApplication.Global()
webApp.RegisterRoutes(routes);


Again Thank You.
Requesting Gravatar... Nick Berardi Jul 08, 2008 3:26 PM
# re: RouteEvaluator For Unit Testing Routes
Thanks for this.

Just one bug I found.

this.routes.GetRouteData(context.Object);

should be

route.GetRouteData(context.Object);
Requesting Gravatar... Matti Lehtinen Sep 20, 2008 5:41 AM
# re: RouteEvaluator For Unit Testing Routes
Thank you Nick about pointing out the bug. Helped me a lot.
Requesting Gravatar... Agus Suhanto Sep 27, 2008 6:34 PM
# re: RouteEvaluator For Unit Testing Routes
Nick was right. Notice that in original code, route variable is not used within iteration block. And if we use this.routes instead of route local variable, the matching route count can be more than 1, but all are the same.
Requesting Gravatar... Agus Suhanto Sep 27, 2008 6:43 PM
# re: RouteEvaluator For Unit Testing Routes
As addition, I think we can do more than just checking the count of matching route. If we want to be so precise to test which route is selected, the following code might be useful:

var urlPattern = "{controller}/{action}";
var matches = evaluator.GetMatches("~/commerce/test");
Assert.IsTrue(matches.Count > 0);
Assert.AreEqual(urlPattern, ((System.Web.Routing.Route)matches[0].Route).Url);

Requesting Gravatar... James World Nov 04, 2008 2:38 PM
# re: RouteEvaluator For Unit Testing Routes

You can go even further than this and assert that particular parameters are correctly caught, eg to test the controller is foo in your example:

Assert.AreEqual(matchingRouteData[0].Values["controller"].ToString(), "foo");

Requesting Gravatar... Gopi Feb 19, 2009 4:42 AM
# re: RouteEvaluator For Unit Testing Routes
Thanks a lot...

Requesting Gravatar... Andri Jul 16, 2010 7:39 AM
# re: RouteEvaluator For Unit Testing Routes
I still try to understand these whole article is all about, since I just learn it. Thanks for your great help. Andri

What do you have to say?

(will show your gravatar)
Please add 2 and 3 and type the answer here: