The problem is that I cannot extract the URL from the route.
I disagree. The problem is what you expect to pull the URLs from the route table and compare them from the outside. In addition, it is unclear what you hope to get by doing this.
Routing compares an incoming request with business logic to determine if it matches. This is the goal of the route . Moving the matching logic outside the route is not a valid test because you are not testing the business logic implemented by the route.
Not to mention that itβs somewhat assumed that the route can only match the URL, and nothing else in the request, for example, the values ββof form messages or cookies. Although the built-in routing function matches only URLs, there is nothing stopping you from making a restriction or custom route that meets other criteria.
So, in short, you need to write unit tests for the business logic in your routes. Any logic that occurs outside of your route configuration must be separately tested.
There is a great article from Brad Wilson (albeit a bit dated) that demonstrates how to unit test your routes. I updated the code to work with MVC 5 - here is a working demo using the code below.
IncomingRouteTests.cs
using Microsoft.VisualStudio.TestTools.UnitTesting; using MvcRouteTesting; using System.Web.Mvc; using System.Web.Routing; [TestClass] public class IncomingRouteTests { [TestMethod] public void RouteWithControllerNoActionNoId() { // Arrange var context = new StubHttpContextForRouting(requestUrl: "~/controller1"); var routes = new RouteCollection(); RouteConfig.RegisterRoutes(routes); // Act RouteData routeData = routes.GetRouteData(context); // Assert Assert.IsNotNull(routeData); Assert.AreEqual("controller1", routeData.Values["controller"]); Assert.AreEqual("Index", routeData.Values["action"]); Assert.AreEqual(UrlParameter.Optional, routeData.Values["id"]); } [TestMethod] public void RouteWithControllerWithActionNoId() { // Arrange var context = new StubHttpContextForRouting(requestUrl: "~/controller1/action2"); var routes = new RouteCollection(); RouteConfig.RegisterRoutes(routes); // Act RouteData routeData = routes.GetRouteData(context); // Assert Assert.IsNotNull(routeData); Assert.AreEqual("controller1", routeData.Values["controller"]); Assert.AreEqual("action2", routeData.Values["action"]); Assert.AreEqual(UrlParameter.Optional, routeData.Values["id"]); } [TestMethod] public void RouteWithControllerWithActionWithId() { // Arrange var context = new StubHttpContextForRouting(requestUrl: "~/controller1/action2/id3"); var routes = new RouteCollection(); RouteConfig.RegisterRoutes(routes); // Act RouteData routeData = routes.GetRouteData(context); // Assert Assert.IsNotNull(routeData); Assert.AreEqual("controller1", routeData.Values["controller"]); Assert.AreEqual("action2", routeData.Values["action"]); Assert.AreEqual("id3", routeData.Values["id"]); } [TestMethod] public void RouteWithTooManySegments() { // Arrange var context = new StubHttpContextForRouting(requestUrl: "~/a/b/c/d"); var routes = new RouteCollection(); RouteConfig.RegisterRoutes(routes); // Act RouteData routeData = routes.GetRouteData(context); // Assert Assert.IsNull(routeData); } [TestMethod] public void RouteForEmbeddedResource() { // Arrange var context = new StubHttpContextForRouting(requestUrl: "~/foo.axd/bar/baz/biff"); var routes = new RouteCollection(); RouteConfig.RegisterRoutes(routes); // Act RouteData routeData = routes.GetRouteData(context); // Assert Assert.IsNotNull(routeData); Assert.IsInstanceOfType(routeData.RouteHandler, typeof(StopRoutingHandler)); } }
OutgoingRouteTests.cs
using Microsoft.VisualStudio.TestTools.UnitTesting; using MvcRouteTesting; using System.Web; using System.Web.Mvc; using System.Web.Routing; [TestClass] public class OutgoingRouteTests { [TestMethod] public void ActionWithAmbientControllerSpecificAction() { UrlHelper helper = GetUrlHelper(); string url = helper.Action("action"); Assert.AreEqual("/defaultcontroller/action", url); } [TestMethod] public void ActionWithSpecificControllerAndAction() { UrlHelper helper = GetUrlHelper(); string url = helper.Action("action", "controller"); Assert.AreEqual("/controller/action", url); } [TestMethod] public void ActionWithSpecificControllerActionAndId() { UrlHelper helper = GetUrlHelper(); string url = helper.Action("action", "controller", new { id = 42 }); Assert.AreEqual("/controller/action/42", url); } [TestMethod] public void RouteUrlWithAmbientValues() { UrlHelper helper = GetUrlHelper(); string url = helper.RouteUrl(new { }); Assert.AreEqual("/defaultcontroller/defaultaction", url); } [TestMethod] public void RouteUrlWithAmbientValuesInSubApplication() { UrlHelper helper = GetUrlHelper(appPath: "/subapp"); string url = helper.RouteUrl(new { }); Assert.AreEqual("/subapp/defaultcontroller/defaultaction", url); } [TestMethod] public void RouteUrlWithNewValuesOverridesAmbientValues() { UrlHelper helper = GetUrlHelper(); string url = helper.RouteUrl(new { controller = "controller", action = "action" }); Assert.AreEqual("/controller/action", url); } static UrlHelper GetUrlHelper(string appPath = "/", RouteCollection routes = null) { if (routes == null) { routes = new RouteCollection(); RouteConfig.RegisterRoutes(routes); } HttpContextBase httpContext = new StubHttpContextForRouting(appPath); RouteData routeData = new RouteData(); routeData.Values.Add("controller", "defaultcontroller"); routeData.Values.Add("action", "defaultaction"); RequestContext requestContext = new RequestContext(httpContext, routeData); UrlHelper helper = new UrlHelper(requestContext, routes); return helper; } }
Stubs.cs
using System; using System.Collections.Specialized; using System.Web; public class StubHttpContextForRouting : HttpContextBase { StubHttpRequestForRouting _request; StubHttpResponseForRouting _response; public StubHttpContextForRouting(string appPath = "/", string requestUrl = "~/") { _request = new StubHttpRequestForRouting(appPath, requestUrl); _response = new StubHttpResponseForRouting(); } public override HttpRequestBase Request { get { return _request; } } public override HttpResponseBase Response { get { return _response; } } public override object GetService(Type serviceType) { return null; } } public class StubHttpRequestForRouting : HttpRequestBase { string _appPath; string _requestUrl; public StubHttpRequestForRouting(string appPath, string requestUrl) { _appPath = appPath; _requestUrl = requestUrl; } public override string ApplicationPath { get { return _appPath; } } public override string AppRelativeCurrentExecutionFilePath { get { return _requestUrl; } } public override string PathInfo { get { return ""; } } public override NameValueCollection ServerVariables { get { return new NameValueCollection(); } } } public class StubHttpResponseForRouting : HttpResponseBase { public override string ApplyAppPathModifier(string virtualPath) { return virtualPath; } }
With this in mind, back to your original question.
How to determine if a url is in a route table?
The question is a bit presumptive. As others have pointed out, the route table does not contain URLs; it contains business logic. A more correct way to express a question would be:
How to determine if the incoming URL matches any route in the route table?
Then you are on your way.
To do this, you need to follow the GetRouteData business logic in the route collection. This will execute the GetRouteData method for each route until the first of them returns a RouteData object instead of null . If none of them returns a RouteData object (i.e., all routes return null ), this means that none of the routes matches the query.
In other words, a null result from GetRouteData indicates that none of the routes matched the query. The RouteData object indicates that one of the routes is consistent and it provides the necessary route data (controller, action, etc.) so that the MVC matches the action method.
So, to just check if the URL matches the route, you just need to determine if the result of the operation is null .
[TestMethod] public void EnsureHomeAboutMatches() {
Note also that route generation is a separate task from matching incoming routes. You can generate outgoing URLs from routes, but it uses a completely different set of business logic than matching inbound routes. This outgoing URL logic can (and should) be tested separately from the incoming URL logic, as shown above.