Asp.net-Mvc

ASP.NET MVC 自定義路由約束、依賴注入和單元測試

  • January 26, 2012

關於這個話題,我問了另一個問題:

ASP.NET MVC 自定義路由約束和依賴注入

這是目前的情況:在我的 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>>()
       ) 
   }
);

在單元測試部分,我使用了以下程式碼:

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

在這裡,我無法弄清楚如何傳遞依賴關係。任何想法?

就我個人而言,我盡量避免在路由約束內執行此類驗證,因為以這種方式表達您的意圖要困難得多。相反,我使用約束來確保參數的格式/類型正確,並將此類邏輯放入我的控制器中。

在您的範例中,我假設如果該國家/地區無效,那麼您將退回到不同的路線(例如“未找到國家/地區”頁面)。與接受所有國家/地區參數並在控制器中檢查它們相比,依靠您的路由配置可靠性要差得多(並且更有可能被破壞):

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

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

除此之外,您在此處嘗試實現的目標(如前所述)實際上是集成測試。當您發現框架的某些部分妨礙了您的測試時,可能是重構的時候了。在您的範例中,我想測試

  1. 國家得到正確驗證
  2. 我的路由配置。

我們可以做的第一件事是將 Country 驗證移到一個單獨的類中:

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

根據我們的路由配置,我們可以驗證有效的國家/地區映射到國家/地區路由,而無效的國家/地區映射到備用路由。

但是,您問題的重點是如何處理路由約束的依賴關係。上面的測試實際上是在測試很多東西——我們的路由配置、路由約束、驗證器以及可能對儲存庫/數據庫的訪問。

如果您依賴 IoC 工具為您注入這些,您將不得不模擬您的驗證器和儲存庫/db,並在您的測試設置中使用您的 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);
   }

這一次,當我們執行路由測試時,我們不是在測試我們的驗證邏輯或數據庫訪問。相反,我們在提供有效或無效國家/地區時對我們的路由配置進行單元測試。

引用自:https://stackoverflow.com/questions/9003736