ASP.NET MVC自定义路由约束,依赖注入和单元测试 [英] ASP.NET MVC Custom Route Constraints, Dependency Injection and Unit Testing

查看:94
本文介绍了ASP.NET MVC自定义路由约束,依赖注入和单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

About this topic, I have asked another question:

<一个href=\"http://stackoverflow.com/questions/8308642/asp-net-mvc-custom-route-constraints-and-dependency-injection\">ASP.NET MVC自定义路由约束和依赖注入

下面是目前的情况:我的ASP.NET MVC 3应用程序,我有类似下面定义的路由约束:

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

在单元测试的一部分,我用下面的code:

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?

推荐答案

我个人尽量避免一个路由约束内执行这样的验证,因为它是更难这种方式前preSS你的意图。相反,我使用限制,以确保参数的正确格式/类型,并把这样的逻辑在我的控制器。

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:

    public ActionResult Country(string country)
    {
        if (country == "france") // lookup to db here
        {
            // valid
            return View();
        }

        // invalid 
        return RedirectToAction("NotFound");
    }

这一边,你要实现的目标在这里(前面已经提到)实际上是一个集成测试。当您发现该框架的部分在测试的方式越来越那么它可能是时间的重构。在你的榜样,我会想测试

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


  1. 各国都能正确验证

  2. 我的路由配置。

我们可以做的第一件事是推动该国确认为一个单独的类:

The first thing we can do is move the Country validation into a separate class:

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

我们就可以测试这个作为一个单元:

We can then test this as a unit:

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

我们映射我们的路线,像这样:

We map our route like so:

routes.MapRoute(
    "Valid Country Route", 
    "countries/{country}", 
    new { controller = "Home", action = "Country" },
    new { country = new CountryRouteConstraint(new CountryValidator()) 
});

现在,如果你真的觉得有必要测试RouteConstraint你可以测试这个独立的:

Now if you really feel it is necessary to test the RouteConstraint you can test this independently:

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

我个人不会理会,因为我们已经抽象验证code所以真的这只是测试框架执行此测试。

Personally I wouldn't bother performing this test as we've already abstracted the validation code so really this is just testing the framework.

要测试路由映射,我们可以使用MvcContrib的 TestHelper

To test the route mapping we can use MvcContrib's 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());
    }

根据我们的路由配置,我们可以验证有效的国家映射到国内航线和一个无效的国家映射到备用路径。

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.

如果你依靠IoC的工具来注入这些对你来说,你将不得不嘲笑你的验证器和存储库/ db和在你测试的设置你的IoC工具注册​​这些。

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:

public interface IRouteConstraintFactory
{
    IRouteConstraint Create<TRouteConstraint>() 
        where TRouteConstraint : IRouteConstraint;
}

您真实的实施可以只使用你的IoC工具创建 IRouteConstraint 实例。

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:

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 much easier. Since we're only interested in testing the country routes we can create a test factory that does only what we need:

    private class TestRouteConstraintFactory : IRouteConstraintFactory
    {
        public IRouteConstraint Create<TRouteConstraint>() where TRouteConstraint : IRouteConstraint
        {
            return new CountryRouteConstraint(new FakeCountryValidator());
        }
    }

请注意,这一次我们使用一个 FakeCountryValidator 包含刚好够逻辑让我们来测试我们的路线:

Note that this time we're using a FakeCountryValidator that contains just enough logic for us to test our routes:

public class FakeCountryValidator : ICountryValidator
{
    public bool IsValid(string country)
    {
        return country.Equals("france", StringComparison.InvariantCultureIgnoreCase);
    }
}

当我们建立了我们的测试中,我们通过了 TestRouteFactoryConstraint 来我们的路线注册表:

When we set up our tests we pass the 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'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.

这篇关于ASP.NET MVC自定义路由约束,依赖注入和单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆