[翻譯] ASP.NET MVC Tip #13 – 對自訂路由進行單元測試
原文地址:http://weblogs.asp.net/stephenwalther/archive/2008/07/02/asp-net-mvc-tip-13-unit-test-your-custom-routes.aspx
翻譯:Anders Liu
摘要:在這個Tip中,Stephen Walther示範了如何為你的ASP.NET MVC應用程式中的自訂路由建立單元測試。Stephen Walther介紹了如何測試一個URL是否被映射到正確的控制器、控制器操作和巨集指令引數上。
在建立ASP.NET MVC應用程式時,如果你是忠於測試驅動開發的,你應該對所有東西進行單元測試。先編寫單元測試,再編寫代碼來滿足測試。重複、重複、重複到吐。
路由是MVC應用程式中的重要部分。路由決定了一個URL如何映射到特定的控制器和控制器操作。由於路由在MVC應用程式中如此重要,所以你需要為路由編寫單元測試。在這個Tip中,我將向你展示如何通過仿製HTTP Context來為路由編寫單元測試。
建立路由表
你可以在Global.asax檔案中為MVC應用程式建立路由。換句話說,它們是定義在GlobalApplication類中的。清單1包含了預設的Global.asax檔案。
清單1 - Global.asax
<br />using System;<br />using System.Collections.Generic;<br />using System.Linq;<br />using System.Web;<br />using System.Web.Mvc;<br />using System.Web.Routing;</p><p>namespace DefaultOne<br />{<br /> public class GlobalApplication : System.Web.HttpApplication<br /> {<br /> public static void RegisterRoutes(RouteCollection routes)<br /> {<br /> routes.IgnoreRoute("{resource}.axd/{*pathInfo}");</p><p> routes.MapRoute(<br /> "Default", // Route name<br /> "{controller}/{action}/{id}", // URL with parameters<br /> new { controller = "Home", action = "Index", id = "" } // Parameter defaults<br /> );</p><p> }</p><p> protected void Application_Start()<br /> {<br /> RegisterRoutes(RouteTable.Routes);<br /> }<br /> }<br />}<br />
預設情況下,一個路由包括名字、路徑和預設路由。路由得到一個URL後會將其中的不同片段映射到特定的控制器、控制器操作和傳遞給操作的參數上。如:
- /Customer/Details/23
- Controller = Customer
- Action = Details
- Id = 23
URL中的第一個片段被映射到控制器名字,第二部分被映射到控制器操作,而最後一部分被映射到名字為Id的參數。
Default路由包含了預設值。如果沒有指定控制器,則使用Home控制器。如果沒有指定操作,則調用Index操作。如果沒有指定Id,則傳第一個Null 字元串。因此,下面的URL將被這樣解釋:
- /
- Controller = Home
- Action = Index
- Id = ""
對很多MVC應用程式來說,預設路由是你經常要用到的一個。然而,你還可以選擇建立自訂路由。例如,清單2中的Global.asax檔案包含了兩個自訂路由。
清單2 - Global.asax
<br />using System;<br />using System.Collections.Generic;<br />using System.Linq;<br />using System.Web;<br />using System.Web.Mvc;<br />using System.Web.Routing;</p><p>namespace Tip13<br />{<br /> public class GlobalApplication : System.Web.HttpApplication<br /> {<br /> public static void RegisterRoutes(RouteCollection routes)<br /> {<br /> routes.IgnoreRoute("{resource}.axd/{*pathInfo}");</p><p> // Route for blog archive<br /> routes.MapRoute(<br /> "Archive", // Name<br /> "Archive/{entryDate}", // URL<br /> new { controller = "Blog", action = "Details" }, // Defaults<br /> new { entryDate = @"\d{2}-\d{2}-\d{4}" } // Constraints<br /> );</p><p> // Default route<br /> routes.MapRoute(<br /> "Default", // Name<br /> "{controller}/{action}/{id}", // URL<br /> new { controller = "Home", action = "Index", id = "" } // Defaults<br /> );</p><p> // Catch all route<br /> routes.MapRoute(<br /> "CatchIt", // Name<br /> "Product/{*values}", // URL<br /> new { controller = "Product", action = "Index" } // Defaults<br /> );</p><p> }</p><p> protected void Application_Start()<br /> {<br /> RegisterRoutes(RouteTable.Routes);<br /> }<br /> }<br />}<br />
清單2中修改過的Global.asax包含了一個新的、名為Archive的路由,用於處理類似下面這樣的對blog文章的請求:
該自訂路由將這個URL映射到名為Blog的控制器並調用Details()操作。日期將作為名為entryDate的參數傳遞到Details()操作。
這個Global.asax檔案還定義了一個catchall路由。catchall路由包含任意數量的片段。例如,catchall路由將會匹配:
- /Product/a
- /Product/a/b
- /Product/a/b/c
以此類推。
對自訂路由進行單元測試
那麼如何測試自訂路由呢?在我從xUnit(http://www.codeplex.com/xUnit)中看到一個MVC單元測試樣本之前我無法指出如何做這樣的單元測試。為了測試自訂路由,你需要仿製HTTP Context。
在上一篇Tip中,我介紹了在對ASP.NET內部對象如工作階段狀態、表單參數和使用者實體/角色進行單元測試時,如何仿製內容物件。如果你還沒有讀過這篇Blog,請訪問下面的頁面:
- http://www.cnblogs.com/AndersLiu/archive/2008/07/26/asp-net-mvc-tip-12-faking-the-controller-context.html
在看過了xUnit的樣本之後,我修改了仿製的內容物件,使其能夠用於對路由進行單元測試。清單3中的單元測試示範了如何對清單2中的Global.asax中包含的自訂路由進行測試。
清單3 - RouteTest.cs
<br />using System;<br />using System.Web.Routing;<br />using Microsoft.VisualStudio.TestTools.UnitTesting;<br />using MvcFakes;<br />using Tip13;</p><p>namespace Tip13Tests.Routes<br />{<br /> [TestClass]<br /> public class RoutesTest<br /> {<br /> [TestMethod]<br /> public void TestDefaultRoute()<br /> {<br /> // Arrange<br /> var routes = new RouteCollection();<br /> GlobalApplication.RegisterRoutes(routes);</p><p> // Act<br /> var context = new FakeHttpContext("~/");<br /> var routeData = routes.GetRouteData(context);</p><p> // Assert<br /> Assert.AreEqual("Home", routeData.Values["controller"], "Default controller is HomeController");<br /> Assert.AreEqual("Index", routeData.Values["action"], "Default action is Index");<br /> Assert.AreEqual(String.Empty, routeData.Values["id"], "Default Id is empty string");<br /> }</p><p> [TestMethod]<br /> public void TestGoodArchiveRoute()<br /> {<br /> // Arrange<br /> var routes = new RouteCollection();<br /> GlobalApplication.RegisterRoutes(routes);</p><p> // Act<br /> var context = new FakeHttpContext("~/Archive/12-25-1966");<br /> var routeData = routes.GetRouteData(context);</p><p> // Assert<br /> Assert.AreEqual("Blog", routeData.Values["controller"], "Controller is Blog");<br /> Assert.AreEqual("Details", routeData.Values["action"], "Action is Details");<br /> Assert.AreEqual("12-25-1966", routeData.Values["entryDate"], "EntryDate is date passed");</p><p> }</p><p> [TestMethod]<br /> public void TestBadArchiveRoute()<br /> {<br /> // Arrange<br /> var routes = new RouteCollection();<br /> GlobalApplication.RegisterRoutes(routes);</p><p> // Act<br /> var context = new FakeHttpContext("~/Archive/something");<br /> var routeData = routes.GetRouteData(context);</p><p> // Assert<br /> Assert.AreNotEqual("Blog", routeData.Values["controller"], "Controller is not Blog");<br /> }</p><p> [TestMethod]<br /> public void TestCatchAllRoute()<br /> {<br /> // Arrange<br /> var routes = new RouteCollection();<br /> GlobalApplication.RegisterRoutes(routes);</p><p> // Act<br /> var context = new FakeHttpContext("~/Product/a/b/c/d");<br /> var routeData = routes.GetRouteData(context);</p><p> // Assert<br /> Assert.AreEqual("a/b/c/d", routeData.Values["values"], "Got catchall values");<br /> }<br /> }<br />}<br />
這是Visual Studio Test(MS Test)單元測試。當然你也可以使用不同的測試架構,如NUnit或xUnit。下面是這個單元測試工作的方式。
首先,建立了一個路由集合,並將其傳遞給Global.asax檔案中定義的RegisterRoutes()方法。Global.asax檔案對應著一個名為GlobalApplication的類。
接下來,建立了一個仿製的HTTP Context,其中包含了待測試的URL。例如,在等一個測試中,URL ~/Archive/12-25-1966被傳遞到仿製的HTTP Context對象的構造器中。仿製的HTTP Context對象是我在Tip #12中建立的仿製MVC對象的修改版。本文後面可以下載到原始碼,其中的MvcFakes項目中包含了這些仿製對象。
接下來,在仿製的上下文上到用了GetRouteData()方法,並返回了路由資料。路由資料表示將URL傳遞給應用程式路由表,經過解釋後得到的結果。換句話說,路由資料是將URL與路由表中的路由進行對比後得到的結果。
最後,該測試判斷路由資料中是否包含需要的值。在第一個測試裡,驗證了控制器名字、控制器操作和Id的值。依照該測試,空的URL ~/應該映射到Home控制器、Index操作,並且Id的值是String.Empty。
第二個測試檢查了類似~/Archive/12-25-1966這樣的請求是否映射到Blog控制器、Details操作,並建立了名為entryDate的操作。
第三個測試檢查了類似~/Archive/something這樣的請求不能映射到Blog控制器。因為該URL不包含恰當的entryDate,所以不能被Blog控制器處理。
最後一個測實驗證了catchall路由能夠正確工作。該測試檢查了~/Product/a/b/c/d得到瞭解析,使得values參數等於a/b/c/d。換句話說,它檢查了catchall控制器的catch-all部分。
小結
在這個Tip中,我向你展示了一種簡單的測試自訂ASP.NET MVC路由的方法。我建議任何時候只要你修改了Global.asax檔案中的預設路由,都應該對其進行單元測試。
此處下載原始碼:http://weblogs.asp.net/blogs/stephenwalther/Downloads/Tip13/Tip13.zip。