ASP.NET MVC Route Unit Test - Part.1 - MvcRouteUnitTester

無論正在開發或已經運行中的 ASP.NET MVC 網站服務,都不單單只有一條 Default Route 設定,在系統逐步長大下,Route 設定也逐漸複雜。
為確保日後「新增」or 「異動」時,不會被自己或他人改壞,加上單元測試來保護,應該是個不錯的作法。

●  2015-12-29 文章更新:加註內文使用「MvcRouteUnitTester」套件,進行測試開發。
●  2016-01-03 文章更新:調整 Source Code 專案結構,更新 Github Repo。
●  2016-01-14 文章更新:修改前言注意事項。

前言

以下就來介紹:使用外部 Library 來進行 Route 的單元測試開發。
因為程式看起來相當語意化,蠻容易上手的,有時間的話,可以嘗試撰寫看看。

以下所紀錄與討論的是「針對 RouteConfig 的測試」,這部分在運行測試時,只會打到 RouteConfig,並不會觸及到實際的 Controller 層。之後會再來紀錄,串通到 Controller 層的內容。
本篇「MvcRouteUnitTester」元件,無法測試 Attribute Routing,可參考 Part.4 另一套「MvcRouteTester」元件的操作。

紀錄

首先來看的是範例專案狀態。(這裡用的專案為:預設的 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 測試的做法。

範例專案:GitHub

結論

因為開發與維護運行許久的應用程式,可能有不少 RouteConfig 設定。
建議大家維護與開發 MVC 程式時,多加上 Route 相關測試,以便提升維護的信心與準確性。

參考資料

MvcRouteUnitTester Source Code:https://mvcrouteunittester.codeplex.com