無論正在開發或已經運行中的 ASP.NET MVC 網站服務,都不單單只有一條 Default Route 設定,在系統逐步長大下,Route 設定也逐漸複雜。
為確保日後「新增」or 「異動」時,不會被自己或他人改壞,加上單元測試來保護,應該是個不錯的作法。
●  2015-12-29 文章更新:加註內文使用「MvcRouteUnitTester」套件,進行測試開發。
●  2016-01-03 文章更新:調整 Source Code 專案結構,更新 Github Repo。
●  2016-01-14 文章更新:修改前言注意事項。
前言
以下就來介紹:使用外部 Library 來進行 Route 的單元測試開發。
因為程式看起來相當語意化,蠻容易上手的,有時間的話,可以嘗試撰寫看看。
紀錄
首先來看的是範例專案狀態。(這裡用的專案為:預設的 MVC 專案,以 HomeController 為例)

RouteConfig
namespace Sample.MVC
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults:
                new
                {
                    controller = "Home",
                    action = "Index",
                    id = UrlParameter.Optional
                }
            );
        }
    }
}
Controller
namespace Sample.MVC.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}
安裝套件:MvcRouteUnitTester
Install-Package MvcRouteUnitTester


MvcRouteUnitTester 方法簽章


MvcRouteUnitTester 驗證簽章
- ShouldMatchRoute:驗證「必須要有符合存在的 Route 設定」
 - ShouldMatchNoRoute:驗證「必須不存在的 Route 設定」
 - ShouldBeIgnored:驗證「必須是 Ignored 狀態的 Route 設定」
 
單元測試範例
依據上面 RouteConfig 設定,「預期 / 實際」都存在「Home/ Index」,所以就來一份單元測試來驗證。
測試案例:HomeIndex_RouteTest_Should_MatchRoue
當傳入「/」= 導到首頁;預設 Route 設定,就會跑到「Home/Index」,故配對後符合結果;得到一個「綠燈」。如下圖:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MvcRouteUnitTester;
using Sample.MVC;
using System.Web.Routing;
namespace Use_MvcRouteUnitTester_To_TestRoute.Routes
{
    [TestClass]
    public class RouteConfigTest
    {
        [TestMethod]
        public void HomeIndex_RouteTest_Should_MatchRoute()
        {
            //// Arrange
            var routes = new RouteCollection();
            RouteConfig.RegisterRoutes(routes);
            var expectedControllerName = "Home";
            var expectedActionName = "Index";
            var routeTester = new RouteTester(routes);
            //// Act
            var requsetInfo = routeTester.WithIncomingRequest("/");
            //// Assert
            requsetInfo.ShouldMatchRoute(expectedControllerName, expectedActionName);
        }
    }
}
接著就將所有與「Home/Index」相關的測試項目補上,我們期望收到「綠燈」測試通過的結果。

[TestMethod]
public void DefaultRoute_RouteTest()
{
    //// Arrange
    var routes = new RouteCollection();
    RouteConfig.RegisterRoutes(routes);
    var routeTester = new RouteTester(routes);
    //// Assert
    routeTester.WithIncomingRequest("/").ShouldMatchRoute("Home", "Index");
    routeTester.WithIncomingRequest("/Home").ShouldMatchRoute("Home", "Index");
    routeTester.WithIncomingRequest("/Home/Index").ShouldMatchRoute("Home", "Index");
}
當然為確保我們測試 Code 有正常的依據 RouteConfig 來運行,我們可以將 RouteConfig 預設 Route 做點小手腳,期望能拿到「紅燈」的結果。
將預設 Route,由「Home/Index」改成「Home/Index2」,在不動測試案例的情況下,會得到以下的結果:
我們預期是「Index」,但實際是「Index2」,所以可以看出來,它真的有依照我們 RouteConfig 的規則載運作;我們也就能慢慢將正式的規則加上。

下面來一段,BDD 測試範例程式
Feature: DefaultRouteFeature
    為了確保網站的預設路由設定正確
    讓運行可以正常運作
Scenario: 檢查出應該存在且正確的預設路由設定
    Given 外部呼叫的網址為 "Home/Index"
    When 在進入網站,經過路由配對流程之後
    Then 配對結果,預期符合Controller為 "Home",Action為 "Index"
    And 且這個比對結果是存在又正確的路由
Scenario: 檢查出不存在的預設路由設定
    Given 外部呼叫的網址為 "Home/Index/1/Test"
    When 在進入網站,經過路由配對流程之後
    Then 配對結果,預期是一個不存在的路由
using MvcRouteUnitTester;
using Sample.MVC;
using System.Web.Routing;
using TechTalk.SpecFlow;
namespace Use_MvcRouteUnitTester_To_TestRoute.Routes
{
    [Binding]
    [Scope(Feature = "DefaultRouteFeature")]
    public class DefaultRouteFeatureSteps
    {
        /// <summary>
        /// ActionName
        /// </summary>
        public string ActionName { get; set; }
        /// <summary>
        /// ApiMethod
        /// </summary>
        public string ApiMethod { get; set; }
        /// <summary>
        /// ControllerName
        /// </summary>
        public string ControllerName { get; set; }
        /// <summary>
        /// RouteTester
        /// </summary>
        public RouteTester RouteTester { get; set; }
        /// <summary>
        /// RequestInfo
        /// </summary>
        public RequestInfo RequestInfo { get; set; }
        /// <summary>
        /// 運行測試前
        /// </summary>
        [BeforeScenario]
        public void BeforeScenario()
        {
            var routes = new RouteCollection();
            RouteConfig.RegisterRoutes(routes);
            this.RouteTester = new RouteTester(routes);
        }
        /// <summary>
        /// 設定ApiMethod
        /// </summary>
        /// <param name="method">method</param>
        [Given(@"外部呼叫的網址為 ""(.*)""")]
        public void Given外部呼叫的網址為(string method)
        {
            this.ApiMethod = method;
        }
        [When(@"在進入網站,經過路由配對流程之後")]
        public void When在進入網站經過路由配對流程之後()
        {
            this.RequestInfo = this.RouteTester.WithIncomingRequest(this.ApiMethod);
        }
        [Then(@"配對結果,預期符合Controller為 ""(.*)"",Action為 ""(.*)""")]
        public void Then配對結果預期符合Controller為Action為(string controllerName, string actionName)
        {
            this.ControllerName = controllerName;
            this.ActionName = actionName;
        }
        [Then(@"且這個比對結果是存在又正確的路由")]
        public void Then且這個比對結果是存在又正確的路由()
        {
            this.RequestInfo.ShouldMatchRoute(this.ControllerName, this.ActionName);
        }
        [Then(@"配對結果,預期是一個不存在的路由")]
        public void Then配對結果預期是一個不存在的路由()
        {
            this.RequestInfo.ShouldMatchNoRoute();
        }
    }
}
綠燈

紅燈

最後補上一份比較複雜的路由設定 + 測試案例


1. 因為撰寫測試程式時,要注意有沒有打錯字。
2. 套件相依於: Moq (≥ 4.0.10827)
當然可以用的 Framework 不只有這一套,接下來,會再有幾篇來紀錄 Route 測試的做法。
結論
因為開發與維護運行許久的應用程式,可能有不少 RouteConfig 設定。
建議大家維護與開發 MVC 程式時,多加上 Route 相關測試,以便提升維護的信心與準確性。
參考資料
MvcRouteUnitTester Source Code:https://mvcrouteunittester.codeplex.com
