就我个人而言,我尝试避免在路由约束内执行此类验证,因为以这种方式表达您的意图要困难得多。相反,我使用约束来确保参数采用正确的格式/类型,并将此类逻辑放入我的控制器中。
在您的示例中,我假设如果该国家/地区无效,那么您将退回到其他路线(例如“未找到国家/地区”页面)。与接受所有国家/地区参数并在控制器中检查它们相比,依赖路由配置的可靠性要低得多(并且更有可能被破坏):
public ActionResult Country(string country)
{
if (country == "france") // lookup to db here
{
// valid
return View();
}
// invalid
return RedirectToAction("NotFound");
}
除此之外,您在这里试图实现的目标(正如已经提到的)实际上是一个集成测试。当您发现框架的某些部分妨碍了您的测试时,那么可能是时候进行重构了。在你的例子中我想测试
- 国家/地区已正确验证
- 我的路由配置。
我们能做的第一件事是将国家/地区验证移到一个单独的类中:
public interface ICountryValidator
{
bool IsValid(string country);
}
public class CountryValidator : ICountryValidator
{
public bool IsValid(string country)
{
// you'll probably want to access your db here
return true;
}
}
然后我们可以将其作为一个单元进行测试:
[Test]
public void Country_validator_test()
{
var validator = new CountryValidator();
// Valid Country
Assert.IsTrue(validator.IsValid("france"));
// Invalid Country
Assert.IsFalse(validator.IsValid("england"));
}
Our CountryRouteConstraint
然后更改为:
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);
}
}
我们像这样绘制我们的路线:
routes.MapRoute(
"Valid Country Route",
"countries/{country}",
new { controller = "Home", action = "Country" },
new { country = new CountryRouteConstraint(new CountryValidator())
});
现在,如果您确实觉得有必要测试 RouteConstraint,您可以独立测试:
[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);
}
就我个人而言,我不会费心执行此测试,因为我们已经抽象了验证代码,所以实际上这只是测试框架。
为了测试路由映射,我们可以使用 MvcContrib测试助手 http://nuget.org/packages/MvcContrib.Mvc3.TestHelper-ci.
[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());
}
根据我们的路由配置,我们可以验证有效的国家/地区映射到国家/地区路由,无效的国家/地区映射到后备路由。
但是,您问题的要点是如何处理路线约束的依赖性。上面的测试实际上是在测试很多东西——我们的路由配置、路由约束、验证器以及可能对存储库/数据库的访问。
如果您依赖 IoC 工具为您注入这些内容,则必须模拟您的验证器和存储库/数据库,并在测试设置中使用 IoC 工具注册它们。
如果我们能够控制约束的创建方式,那就更好了:
public interface IRouteConstraintFactory
{
IRouteConstraint Create<TRouteConstraint>()
where TRouteConstraint : IRouteConstraint;
}
您的“真正”实现只需使用 IoC 工具来创建IRouteConstraint
实例。
我喜欢将路由配置放在一个单独的类中,如下所示:
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" });
}
}
可以使用工厂创建具有外部依赖关系的约束。
这使得测试变得更加容易。由于我们只对测试国家/地区路线感兴趣,因此我们可以创建一个仅执行我们需要的操作的测试工厂:
private class TestRouteConstraintFactory : IRouteConstraintFactory
{
public IRouteConstraint Create<TRouteConstraint>() where TRouteConstraint : IRouteConstraint
{
return new CountryRouteConstraint(new FakeCountryValidator());
}
}
请注意,这次我们使用的是FakeCountryValidator
其中包含足够的逻辑供我们测试我们的路线:
public class FakeCountryValidator : ICountryValidator
{
public bool IsValid(string country)
{
return country.Equals("france", StringComparison.InvariantCultureIgnoreCase);
}
}
当我们设置测试时,我们通过了TestRouteFactoryConstraint
到我们的路线注册表:
[SetUp]
public void SetUp()
{
new MyRouteRegistry(new TestRouteConstraintFactory()).RegisterRoutes(RouteTable.Routes);
}
这次当我们运行路由测试时not测试我们的验证逻辑或数据库访问。相反,当提供有效或无效的国家/地区时,我们将对路由配置进行单元测试。