ASP.NET MVC Route Unit Test - Part.2 - MvcRouteTester - MVC Project

於上篇 ASP.NET MVC Route Unit Test - Part.1 的內容中,提到如何使用「MvcRouteUnitTester」元件來測試 RouteConfig。
在文章紀錄中,會發現一個情況。那就是 Route 的測試案例,只環繞在 RouteConfig 中,並沒有接觸到 Controller 層。

接下來這篇會使用 MVC 5 專案 + 另一套 Library,紀錄如何結合 Route 與 Controller 的測試開發。

前言

新建 「Use-MvcRouteTester-To-TestRoute」測試專案。

範例專案:GitHub

以下就來介紹如何使用「MvcRouteTester」進行 Route 的單元測試。(名稱跟前一篇介紹的很類似)
在 NuGet 中可以找到以下Library:https://www.nuget.org/profiles/AnthonySteele

這裡依然使用原來的範本專案 ( ASP.NET MVC5 ),所以將安裝 MvcRouteTester.Mvc5.2

Install-Package MvcRouteTester.Mvc5.2

注意:
1. 請依據實際 MVC 專案版本進行安裝。
2. MvcRouteTester 相依於 .Json.Net 與 ASP.NET MVC。( 建議也將 ASP.NET WebAPI 一同安裝到測試專案)

紀錄

MvcRouteTester 採用 Fluent Extensions 描述方式,使用上語意簡單 + 功能多,這裡簡單介紹:

Web Route Test 簽章

  • RouteAssert.HasRoute
  • RouteAssert.IsIgnoredRoute
  • RouteAssert.NoRoute
  • RouteAssert.GeneratesActionUrl (適用於確認 Url.Action 的結果)
  • RouteCollection 擴充功能:ShouldMap (重點項目,可與 Controller 層一起測試)

ASP.NET MVC 範例

var routes = new RouteCollection();
routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });

//// 驗證/home/index是否符合規則
RouteAssert.HasRoute(routes, "/home/index");

//// 驗證/home/Index/42是否符合規則
var expectedRoute = new { controller = "Home", action = "Index", id = 42 };
RouteAssert.HasRoute(routes, "/home/Index/42", expectedRoute);

ASP.NET MVC Area 範例

RouteAssert.HasRoute(routes, "/SampleArea", new { Area = "SampleArea", Controller = "Home", Action = "Index" });

看到了官方的使用教學後,嘗試寫了一個小段測試

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MvcRouteTester;
using Sample.MVC;
using System;
using System.Web.Routing;

namespace Use_MvcRouteTester_To_TestRoute.Routes
{
    [TestClass]
    public class RouteConfigTest_ByMvcRouteTester : IDisposable
    {
        private RouteCollection testRoutes;

        public RouteConfigTest_ByMvcRouteTester()
        {
            //// Arrange
            testRoutes = new RouteCollection();
            RouteConfig.RegisterRoutes(testRoutes);
        }

        public void Dispose()
        {
            testRoutes.Clear();
        }

        [TestMethod]
        public void DefaultRouteTest()
        {
            //// Assert
            RouteAssert.HasRoute(testRoutes, "/", "Home", "Index");
            RouteAssert.HasRoute(testRoutes, "/Home", "Home", "Index");
            RouteAssert.HasRoute(testRoutes, "/Home/Index", "Home", "Index");
        }
    }
}

測試通過 - 綠燈

刻意修改預設 Route To Home/Index2 - 測試不過 - 紅燈

那麼結合 Controller 的測試案例,如下:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MvcRouteTester;
using Sample.MVC;
using Sample.MVC.Controllers;
using System;
using System.Web.Routing;

namespace Use_MvcRouteTester_To_TestRoute.Routes
{
    [TestClass]
    public class RouteConfigTest_ByMvcRouteTester : IDisposable
    {
        private RouteCollection testRoutes;

        public RouteConfigTest_ByMvcRouteTester()
        {
            //// Arrange
            testRoutes = new RouteCollection();
            RouteConfig.RegisterRoutes(testRoutes);
        }

        public void Dispose()
        {
            testRoutes.Clear();
        }

        /// <summary>
        /// 預設路由 - 根目錄測試 (無指定Controller、Index、Id)
        /// </summary>
        [TestMethod]
        public void DefaultRoute_RootWithNoControllerNoActionNoId_ShouldMapToIndex()
        {
            testRoutes.ShouldMap("/").To<HomeController>(c => c.Index());
        }

        /// <summary>
        /// Home 測試 (無指定Index、Id)
        /// </summary>
        [TestMethod]
        public void Home_RouteWithControllerNoActionNoId_ShouldMapToIndex()
        {
            testRoutes.ShouldMap("/Home").To<HomeController>(c => c.Index());
        }

        /// <summary>
        /// Home/Index 測試 (無指定Id)
        /// </summary>
        [TestMethod]
        public void HomeIndex_RouteWithControllerIndexActionNoId_ShouldMapToIndex()
        {
            testRoutes.ShouldMap("/Home/Index").To<HomeController>(c => c.Index());
        }
    }
}

以上,HomeController 測試結束。

接下來針對 ImagesController 來做測試。新增 MVC 專案的 MapImages Route:

private static void MapImages(RouteCollection routes)
{
    routes.MapRoute(
        name: "RenderOriginal",
        url: "Images/Original/{type}/{id}/{version}",
        defaults:
        new
        {
            controller = "Images",
            action = "RenderOriginal",
            id = UrlParameter.Optional,
            version = "0"
        }
    );
}

可以看到,這樣就將 ImagesController 串起來。

不過...在測試此項目時,發現一個問題:Images - RenderOriginal 有明確的標記 HttpGet
若刻意的放入 HttpMethod.Post 的簽章,測試結果居然正常 ! 這情況只發生在 MVC 專案,而 WebAPI 專案,會正常判別 HttpMethod

這幾天再來爬文一下,確認這個問題是不是自己誤用了...

以上是針對 ASP.NET MVC 5 專案 ( Sample.MVC ) 的 RouteConfig 與 Controller 進行測試開發。

因為這套 Library 的功能相當多,以上測試的方式也相當粗淺,若大家在實務上使用起來有什麼疑問,歡迎一起討論。

結論

上述的介紹與實作,實務運用上,自己覺得更佳方便。
假設改了 ControllerName or ActionName or RouteConfig 等設定;編譯就會失敗 or 測試不過。環環相扣,更能提早發現問題。
有空真的能多嘗試這套 Library,如何在實務上運作。:)

而在測試開發的學習路上,會接觸到「粒度」大小的議題來討論如何進行單元測試。
因為我們目標對象是 RouteConfig,雖然 Controller 跟 Route 有非常密切的關連性,但面對從外部呼叫服務時,接觸到網路、RouteConfig、Controller 等不同環節時,就「粒度小」條件下,為了讓發生問題時,能更快的分辨出是哪一環節出了狀況,盡快修正。

那麼是否要將 RouteConfig 與 Controller 分開進行單元測試 or 合併處理,目前自己還沒有抓到準則,究竟哪種方式比較好,很值得自己、團隊來一同思考與討論。

參考資料

●  MvcRouteTester GitHub
●  MvcRouteTester Wiki Usage
●  Simple Single Line ASP.NET MVC Route Testing
●  Testing Routes In ASP.NET MVC