Asp.net mvc ASP.NET MVC自定义路由约束、依赖项注入和单元测试

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

关于这个话题,我问了另一个问题:

以下是当前情况:在我的ASP.NET MVC 3应用程序上,我定义了一个路由约束,如下所示:

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);
        }
    

    这次当我们运行路由测试时,我们不是在测试验证逻辑或数据库访问。相反,当提供了有效或无效的国家/地区时,我们正在对路由配置进行单元测试。

    @chrismaric
    icontyrepository
    to
    CountryRouteConstraint
    自定义路由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);
        }