Personally, I try to avoid performing such a check as part of the route restriction, as it is much more difficult to express your intentions. Instead, I use constraints to ensure that the parameters are in the correct format / type and put such logic into my controllers.
In your example, I assume that if the country is invalid, you will return to another route (say, on the page "Country not found"). Relying on your routing configuration is much less reliable (and most likely will be broken) than accepting all country parameters and checking them in your controller:
public ActionResult Country(string country) { if (country == "france")
Which aside, what you are trying to achieve here (as already mentioned) is actually an integration test. When you find that parts of the framework interfere with your tests, this may be time for refactoring. In your example, I would like to test
- That countries are correctly approved.
- My routing configuration.
The first thing we can do is translate the country check into a separate class:
public interface ICountryValidator { bool IsValid(string country); } public class CountryValidator : ICountryValidator { public bool IsValid(string country) {
Then we can check this as a unit:
[Test] public void Country_validator_test() { var validator = new CountryValidator();
Our CountryRouteConstraint
will then change to:
public class CountryRouteConstraint : IRouteConstraint { private readonly ICountryValidator countryValidator; public CountryRouteConstraint(ICountryValidator countryValidator) { this.countryValidator = countryValidator; } public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { object country = null; values.TryGetValue("country", out country); return countryValidator.IsValid(country as string); } }
We configure our route as follows:
routes.MapRoute( "Valid Country Route", "countries/{country}", new { controller = "Home", action = "Country" }, new { country = new CountryRouteConstraint(new CountryValidator()) });
Now, if you really feel the need to test RouteConstraint, you can check it yourself:
[Test] public void RouteContraint_test() { var constraint = new CountryRouteConstraint(new CountryValidator()); var testRoute = new Route("countries/{country}", new RouteValueDictionary(new { controller = "Home", action = "Country" }), new RouteValueDictionary(new { country = constraint }), new MvcRouteHandler()); var match = constraint.Match(GetTestContext(), testRoute, "country", new RouteValueDictionary(new { country = "france" }), RouteDirection.IncomingRequest); Assert.IsTrue(match); }
Personally, I would not do this test, since we have already abstracted the verification code, so this is just a framework check.
To check the route mapping, we can use the MvcContrib TestHelper .
[Test] public void Valid_country_maps_to_country_route() { "~/countries/france".ShouldMapTo<HomeController>(x => x.Country("france")); } [Test] public void Invalid_country_falls_back_to_default_route() { "~/countries/england".ShouldMapTo<HomeController>(x => x.Index()); }
According to our routing configuration, we can verify that the actual country is being mapped to the country’s route and that the country’s map for the backup route is incorrect.
However, the main question of your question was how to handle route constraint dependencies. The above test actually checks a number of things: our routing configuration, route restriction, validator, and possibly access to the repository / database.
If you rely on the IoC tool to implement them, you will need to make fun of your validator and / db repository and register them using the IoC tool in your test setup.
It would be better if we could control how restrictions are created:
public interface IRouteConstraintFactory { IRouteConstraint Create<TRouteConstraint>() where TRouteConstraint : IRouteConstraint; }
Your “real” implementation can simply use your IoC tool to instantiate IRouteConstraint
.
I like to transfer the routing configuration to a separate class as follows:
public interface IRouteRegistry { void RegisterRoutes(RouteCollection routes); } public class MyRouteRegistry : IRouteRegistry { private readonly IRouteConstraintFactory routeConstraintFactory; public MyRouteRegistry(IRouteConstraintFactory routeConstraintFactory) { this.routeConstraintFactory = routeConstraintFactory; } public void RegisterRoutes(RouteCollection routes) { routes.MapRoute( "Valid Country", "countries/{country}", new { controller = "Home", action = "Country" }, new { country = routeConstraintFactory.Create<CountryRouteConstraint>() }); routes.MapRoute("Invalid Country", "countries/{country}", new { controller = "Home", action = "index" }); } }
Constraints with external dependencies can be created using the factory.
This makes testing easier. Since we are only interested in testing the routes of the country, we can create a factory test that does only what we need:
private class TestRouteConstraintFactory : IRouteConstraintFactory { public IRouteConstraint Create<TRouteConstraint>() where TRouteConstraint : IRouteConstraint { return new CountryRouteConstraint(new FakeCountryValidator()); } }
Please note that this time we are using the FakeCountryValidator
, which contains enough logic to test our routes:
public class FakeCountryValidator : ICountryValidator { public bool IsValid(string country) { return country.Equals("france", StringComparison.InvariantCultureIgnoreCase); } }
When we set up our tests, we pass TestRouteFactoryConstraint
to our route registry:
[SetUp] public void SetUp() { new MyRouteRegistry(new TestRouteConstraintFactory()).RegisterRoutes(RouteTable.Routes); }
This time, when we run our routing tests, we do not test our validation logic or database access. Instead, we test our routing configuration when a valid or invalid country is provided.