About this topic, I have asked another question:
ASP.NET MVC Custom Route Constraints and Dependency Injection
Here is the current situation: on my ASP.NET MVC 3 App, I have a route constraint defined like below:
public class CountryRouteConstraint : IRouteConstraint {
private readonly ICountryRepository<Country> _countryRepo;
public CountryRouteConstraint(ICountryRepository<Country> countryRepo) {
_countryRepo = countryRepo;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {
//do the database look-up here
//return the result according the value you got from DB
return true;
}
}
I am using this like below:
routes.MapRoute(
"Countries",
"countries/{country}",
new {
controller = "Countries",
action = "Index"
},
new {
country = new CountryRouteConstraint(
DependencyResolver.Current.GetService<ICountryRepository<Country>>()
)
}
);
At the unit testing part, I used the below code:
[Fact]
public void country_route_should_pass() {
var mockContext = new Mock<HttpContextBase>();
mockContext.Setup(c => c.Request.AppRelativeCurrentExecutionFilePath).Returns("~/countries/italy");
var routes = new RouteCollection();
TugberkUgurlu.ReservationHub.Web.Routes.RegisterRoutes(routes);
RouteData routeData = routes.GetRouteData(mockContext.Object);
Assert.NotNull(routeData);
Assert.Equal("Countries", routeData.Values["controller"]);
Assert.Equal("Index", routeData.Values["action"]);
Assert.Equal("italy", routeData.Values["country"]);
}
Here, I cannot figure out how to pass the dependency. Any idea?
Now with the information you've provided, the actual dependency you're concerned about is the dependency to
DependencyResolver
(anyone else find some irony in that?).You will want to do something like
DependencyResolver.SetResolver(mockContext2.Object);
Prior to your usage of the routing setup.
Added information:
Arguably your code be cleaner if you changed
To have that be contained in the class itself
As then you would just new up the
CountryRouteConstraint
. This is generally the traditional implementation of Poorman's DI. While it does obscure the dependency toDependencyResolver
1 step further I feel that's pretty fine. It keeps with the convention for Poorman's DI and it would give you more expected behaviour.If you had the class constructed as such above, when you had reached your unit test you would most likely have gotten an exception that
DependencyResolver
does not know how to activateICountryRepository<Country>
which would push you in the obvious direction of fixing that. Although I suppose you probably got the same exception since you directly called theDependencyResolver
, still very noisey to need to writeDependencyResolver.Current.GetService<ICountryRepository<Country>>()
more than once.Personally I try and avoid performing such validation within a route constraint as it is much harder to express your intentions this way. Instead I use constraints to ensure parameters are in the correct format/type and put such logic in my controllers.
In your example I'm assuming that if the country is not valid then you will fall back to a different route (say to a "Country Not Found" page). Relying on your routing configuration is much less reliable (and much more likely to be broken) than accepting all country parameters and checking them in your controller:
That aside, what you are trying to achieve here (as has already been mentioned) is actually an integration test. When you find that parts of the framework are getting in the way of your tests then it could be time for a refactor. In your example I would want to test
The first thing we can do is move the Country validation into a separate class:
We can then test this as a unit:
Our
CountryRouteConstraint
then changes to:We map our route like so:
Now if you really feel it is necessary to test the RouteConstraint you can test this independently:
Personally I wouldn't bother performing this test as we've already abstracted the validation code so really this is just testing the framework.
To test the route mapping we can use MvcContrib's TestHelper.
Based on our routing configuration we can verify that a valid country maps to the country route and an invalid country maps to the fallback route.
However, the main point of your question was how to handle the dependencies of route constraints. The test above is actually testing a number of things - our routing configuration, route constraint, validator and probably access to a repository/database.
If you're relying on a IoC tool to inject these for you, you're going to have to mock your validator and repository/db and register these with your IoC tool in the set up of your tests.
It would be better if we could control how constraints are created:
Your "real" implementation can just use your IoC tool to create the
IRouteConstraint
instance.I like to put my routing configuration in a separate class like so:
Constraints with external dependencies can be created using the factory.
This makes testing much easier. Since we're only interested in testing the country routes we can create a test factory that does only what we need:
Note that this time we're using a
FakeCountryValidator
that contains just enough logic for us to test our routes:When we set up our tests we pass the
TestRouteFactoryConstraint
to our route registry:This time when we run our routing tests we're not testing our validation logic or database access. Instead we are unit testing our routing configuration when either a valid or invalid country is provided.
What are you testing? It seems to me that you only need to unit test your constraint, not the routing engine. In that case, you should be instantiating your constraint and testing it's
Match
method. Once you know that your constraint works you can do some manual testing to ensure that your route is mapped correctly. That will probably be necessary to ensure the proper ordering of your routes so that you don't match too early (or late) in the set anyway.