Asp.net mvc ASP.NET MVC自定义路由约束、依赖项注入和单元测试
关于这个话题,我问了另一个问题: 以下是当前情况:在我的ASP.NET MVC 3应用程序上,我定义了一个路由约束,如下所示:Asp.net mvc ASP.NET MVC自定义路由约束、依赖项注入和单元测试,asp.net-mvc,asp.net-mvc-3,unit-testing,dependency-injection,moq,Asp.net Mvc,Asp.net Mvc 3,Unit Testing,Dependency Injection,Moq,关于这个话题,我问了另一个问题: 以下是当前情况:在我的ASP.NET MVC 3应用程序上,我定义了一个路由约束,如下所示: public class CountryRouteConstraint : IRouteConstraint { private readonly ICountryRepository<Country> _countryRepo; public CountryRouteConstraint(ICountryRepository<C
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;
}
}
routes.MapRoute(
"Countries",
"countries/{country}",
new {
controller = "Countries",
action = "Index"
},
new {
country = new CountryRouteConstraint(
DependencyResolver.Current.GetService<ICountryRepository<Country>>()
)
}
);
公共类CountryRouteConstraint:IRouteConstraint{
私人只读ICountryRepository\u countryRepo;
公共CountryRouteConstraint(IContryRepository countryRepo){
_countryRepo=countryRepo;
}
公共布尔匹配(HttpContextBase httpContext、路由路由、字符串参数名称、RouteValueDictionary值、RouteDirection RouteDirection){
//数据库在这里查找吗
//根据从DB获得的值返回结果
返回true;
}
}
我使用的方法如下:
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;
}
}
routes.MapRoute(
"Countries",
"countries/{country}",
new {
controller = "Countries",
action = "Index"
},
new {
country = new CountryRouteConstraint(
DependencyResolver.Current.GetService<ICountryRepository<Country>>()
)
}
);
routes.MapRoute(
“国家”,
“国家/{country}”,
新{
controller=“国家”,
action=“Index”
},
新{
country=新的CountryRouteConstraint(
DependencyResolver.Current.GetService()
)
}
);
在单元测试部分,我使用了以下代码:
[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"]);
}
[事实]
公共无效国家/地区/路线/应通过(){
var mockContext=new Mock();
mockContext.Setup(c=>c.Request.AppRelativeCurrentExecutionFilePath)。返回(“~/countries/italy”);
var routes=new RouteCollection();
TugberkUgurlu.ReservationHub.Web.Routes.RegisterRoutes(Routes);
RouteData RouteData=routes.GetRouteData(mockContext.Object);
Assert.NotNull(routeData);
Assert.Equal(“国家”,路由数据值[“控制器”]);
Assert.Equal(“Index”,routeData.Values[“action”]);
Assert.Equal(“意大利”,routeData.Values[“国家]);
}
在这里,我不知道如何传递依赖项。有什么想法吗?你在测试什么?在我看来,您只需要单元测试您的约束,而不需要路由引擎。在这种情况下,您应该实例化约束并测试它的
Match
方法。一旦知道约束起作用,就可以进行一些手动测试,以确保正确映射路线。这可能是必要的,以确保路由的正确顺序,这样您就不会在集合中过早(或较晚)匹配。现在,根据您提供的信息,您关心的实际依赖关系是对依赖解析程序的依赖关系(还有人发现这一点吗?)
你会想做一些类似的事情
var mockContext2 = new Mock<IDependencyResolver>();
mockContext2.Setup(c =>
c.GetService(It.Is.Any<ICountryRepository<Country>>())
.Returns(____ whatever you want);
将其包含在类本身中
public CountryRouteConstraint() :
this(DependencyResolver.Current.GetService<ICountryRepository<Country>>()) {}
public CountryRouteConstraint(ICountryRepository<Country> repository) {}
public CountryRouteConstraint():
此(DependencyResolver.Current.GetService()){}
公共CountryRouteConstraint(ICountryRepository存储库){}
然后您就可以新建CountryRouteConstraint
。这通常是Poorman的DI的传统实现。虽然它确实模糊了对DependencyResolver
1的依赖关系,但我觉得这很好。它与Poorman的DI的约定保持一致,并将为您提供更多预期的行为
如果您按照上述方式构造了类,那么当您进行单元测试时,您很可能会遇到一个异常,DependencyResolver
不知道如何激活IContyrepository
,这会将您推向修复该异常的明显方向。虽然我认为您可能会遇到相同的异常,因为您直接调用了dependencysolver
,但仍然需要编写dependencysolver.Current.GetService()来处理
不止一次。就我个人而言,我尽量避免在路由约束内执行此类验证,因为用这种方式表达您的意图要困难得多。相反,我使用约束来确保参数的格式/类型正确,并将此类逻辑放入控制器中
在您的示例中,我假设如果国家/地区无效,那么您将退回到另一条路线(比如“未找到国家/地区”页面)。与接受所有国家/地区参数并在控制器中检查它们相比,依赖您的路由配置的可靠性要低得多(并且更可能被破坏):
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"));
}
我们的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
可以使用工厂创建具有外部依赖关系的约束
这使得测试更加容易。由于我们只对测试乡村路线感兴趣,我们可以创建一个只做我们需要的测试工厂:
private class TestRouteConstraintFactory : IRouteConstraintFactory
{
public IRouteConstraint Create<TRouteConstraint>() where TRouteConstraint : IRouteConstraint
{
return new CountryRouteConstraint(new FakeCountryValidator());
}
}
设置测试时,我们将TestRouteFactoryConstraint
传递到路由注册表:
[SetUp]
public void SetUp()
{
new MyRouteRegistry(new TestRouteConstraintFactory()).RegisterRoutes(RouteTable.Routes);
}
这次当我们运行路由测试时,我们不是在测试验证逻辑或数据库访问。相反,当提供了有效或无效的国家/地区时,我们正在对路由配置进行单元测试。@chrismaricicontyrepository
toCountryRouteConstraint
自定义路由constraint@ChrisMarisic基本上,在我的单元测试项目中,我应该替换country
route约束
是执行代码块的包装器吗
private class TestRouteConstraintFactory : IRouteConstraintFactory
{
public IRouteConstraint Create<TRouteConstraint>() where TRouteConstraint : IRouteConstraint
{
return new CountryRouteConstraint(new FakeCountryValidator());
}
}
public class FakeCountryValidator : ICountryValidator
{
public bool IsValid(string country)
{
return country.Equals("france", StringComparison.InvariantCultureIgnoreCase);
}
}
[SetUp]
public void SetUp()
{
new MyRouteRegistry(new TestRouteConstraintFactory()).RegisterRoutes(RouteTable.Routes);
}