只有累積,沒有奇蹟

2021年11月17日 星期三

[NETCore] 如何在 ASP.NET Core Middleware 加上單元測試 Unititest

前言
Middleware 在 ASP.NET Core 開發時是個很常見的功能,概念很像 ASP.NET Application Life cycle 管線的 Handler 機制 (若對於 Life Cycle 想了解更多可以看之前寫的文章 Application Life Cycle),提供開發者可以在 Request 進入到 Application 前加上客製化的邏輯,實務上用起來挺方便的也蠻好用的,在加上 middleware 相關邏輯後也會加上單元測試,確保所撰寫的邏輯有驗證過且是沒有問題,也避免在 middleware 異常時 Devops 同仁半夜叫你起床處理(萬萬不可阿,這篇文章要說明的是如何在 middleware 加上 unittest,對有經驗的開發者相信是一塊小蛋糕,內容若有問題歡迎提出來一起討論。


建立檢查 Token Middleware
舉一個常用的例子,實務上在與第三方在做串接時需要 token 作為對方請求的驗證,作為過濾不必要的請求以節省伺服器的資源。全部 API 接口都需要做 token 檢查因此將此邏輯檢查可以放在 Middleware,首先建立一個新的 .NET Core 3.1 WebAPI 專案,接著建立 TokenVerificationMiddleware.cs 類別,在此類別中加入下列代碼
  1. public class TokenVerificationMiddleware
  2. {
  3. private readonly RequestDelegate _next;
  4.  
  5. public TokenVerificationMiddleware(RequestDelegate next)
  6. {
  7. _next = next;
  8. }
  9.  
  10. public async Task Invoke(HttpContext context)
  11. {
  12. if (context.Request.Headers.ContainsKey("token") && context.Request.Headers["token"] == "marcusblog")
  13. {
  14. await _next.Invoke(context);
  15. }
  16. else
  17. {
  18. context.Response.StatusCode = 403; // UnAuthorized
  19. await context.Response.WriteAsync("Invalid request token");
  20. return;
  21. }
  22. }
  23. }
在此類別的代碼中定義了當請求來時檢查 Request 的 Header 資訊是否有包含 token 關鍵字,token 有的話是否等於 marcusblog,如果都符合就讓此請求往下一步進行進入到下一個流程像是 Action 邏輯;相反的如果不符合 token 檢查邏輯,就會回傳 403 等 Status Code。
當建立好檢查 Token 的 Middleware 之後,接著開啟專案中的 StartUp.cs 類別在 Configure 方法中加上以下代碼
  1. app.UseMiddleware();
上述是使用內建的 UseMiddleware 方法並定義指定的 Middleware 類別,另外還可以使用擴充方法(Extensions Method)達到使用 Middleware,也是個人比較推薦的方式,將要使用的 Middleware 統一集中在特定的class中代碼在閱讀上較為容易理解,使用方式是新增 ApplicationBuilderExtension 類別,裡面新增 UseTokenVerificationMiddleware 方法並定義回傳 IApplicationBuilder 型別,內容與在 startup.cs 的一樣
  1. public static class ApplicationBuilderExtension
  2. {
  3. public static IApplicationBuilder UseTokenVerificationMiddleware(this IApplicationBuilder builder)
  4. {
  5. return builder.UseMiddleware();
  6. }
  7. }
接著再回到 Configure 方法將 UseTokenVerificationMiddleware 取代原本的方法
  1. //app.UseMiddleware();
  2. app.UseTokenVerificationMiddleware();
開啟偵錯進行測試,可以發現網頁打開會因為 Request 中沒有在 Header 中帶 token 參數出現 Invalid request token,畫面如下


加上單元測試
一般來說請求進到 Application 的 Request 請求的內容會存在於 HttpContext 中,接著再把 HttpContext 的請求傳遞到所指定的 Middleware 邏輯中,也就是在建構子中的參數 HttpContext,那麼要進行單元測試的話第一步是要先模擬請求的 HttpContext 物件,這邊可以使用 DefaultHttpContext 達到這目的,DefaultHttpContext 繼承了 HttpContext 抽象類別,讓我們在單元測試中更為方便,舉例來說如果要模擬 hpptContext 物件並在 Header 加上 token 內容用下列方式就可以達到
  1. var context = new DefaultHttpContext();
  2. context.Request.Headers.Add("token", "this is a book");
DefaultHttpContext 還可以指定測試的路徑(Path)、內容(Body)、ContentType 或是可以指定 Cookie Form 及 QueryString 等常見的設定值,使用上相當的簡易與方便,放在測試案例中代碼如下
  1. public class TokenVerificationMiddlewareTest
  2. {
  3. private TokenVerificationMiddleware _target;
  4. [SetUp]
  5. public void Initial()
  6. {
  7. _target = new TokenVerificationMiddleware(null);
  8. }
  9.  
  10. [Test]
  11. public void Correct_Header_Should_Return_Success()
  12. {
  13. var context = new DefaultHttpContext();
  14. context.Request.Headers.Add("token", "marcusblog");
  15.  
  16. var result = _target.Invoke(context);
  17. context.Response.StatusCode.Should().Be(200);
  18. }
  19.  
  20. [Test]
  21. public void Without_Header_Should_Return_UnAuthorized()
  22. {
  23. var context = new DefaultHttpContext();
  24.  
  25. var result = _target.Invoke(context);
  26. context.Response.StatusCode.Should().Be(403);
  27. }
  28.  
  29. [Test]
  30. public void Empty_Header_Should_Return_UnAuthorized()
  31. {
  32. var context = new DefaultHttpContext();
  33.  
  34. var result = _target.Invoke(context);
  35. context.Response.StatusCode.Should().Be(403);
  36. }
  37.  
  38. [Test]
  39. public void Wrong_Header_Should_Return_UnAuthorized()
  40. {
  41. var context = new DefaultHttpContext();
  42. context.Request.Headers.Add("token", "wrongtoken");
  43.  
  44. var result = _target.Invoke(context);
  45. context.Response.StatusCode.Should().Be(403);
  46. }
  47. }
以上是使用 nUnit 搭配 FluentAssertions 套件,測試幾種常見的情境像是 token 為空、header 為空、帶錯誤的 token及帶正確的 token 值 測試後的結果 Pass 成功 !
大功告成,打完收工

感想
在 ASP.NET Core 有了 httpDefaultContext 之後,在針對 Middleware 撰寫單元測試變得方便許多,對有經驗的開發者來說也是小蛋糕一塊,如果有不清楚的地方歡迎一起討論,hope it helps !

參考
為 .NET Core Middleware 加上 Unit Test
DefaultHttpContext Class
ASP.NET Core 執行原理解剖[4]:進入HttpContext的世界
ASP.NET Core 中介軟體
ASP.NET Core 2.1 middlewares part 2: Unit test a custom middleware




Related Posts:

  • [UnitTest] 如何測試目標方法中 Guid 型別的代碼 ?情境 如果要產生一個亂數時很常會想到 Guid 方法解決,在 C# 使用 Guid 的方式相當簡單僅要透過  Guid.NewGuid  靜態方法產生一組 Guid 使用,在目前公司很常看到使用 Guid 作為識別碼,今天在重構舊代碼時忽然想到如果遇到 Guid 該如何進行加上單元測試,以下就目前想到的解法進行測試與說明,如果各位高手們有更好的方法歡迎高抬貴手一起討論研究。 解決方案 寫個簡單的 Sa… Read More
  • [UnitTest] Refactoring 好幫手 - Refactoring.Guru前言 過去還在菜鳥時期很常遇過一種情境,就是當看到一段既有的代碼或是專案裡的 Code,看起來有些怪異的地方但又說不出來怪的點是哪一點,想改又不知道從何下手的情境,如果遇到這問題想要得到解答的話,可以試試看到 refactoring.guru,這網站透過漫畫的方式介紹兩項開發者在 Coding 時的必修課程,重構 ( Refacting ) 與設計模式 ( Design Pattern );以下就針對這兩項目做簡單說明 Re… Read More
  • [UnitTest] ASP.NET Core 2.2 測試專案中的版本衝突 問題  這幾天專案某項功能接近尾聲,要替其核心 ASP.NET Core 專案加上單元測試專案,加入後按下建置發現跳出 Error 錯誤訊息  "CS1705 Assembly 'xxxx, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' uses 'Microsoft.Extensions.Logging.Abstractions, Version=2.… Read More
  • [UnitTest] 使用 Fluent Assertions 增加單元測試碼可讀性前言 過去在撰寫單元測試代碼時都是使用 NUnit 內建的 Assert.AreEqual 來驗證是否符合預期,雖然早已聽過 Fluent Assertions 盛名但並未實際使用過,直到最近在與同事討論時同事大推發現真的很不錯,讓戴碼的可能性增加不少,想起之前上 91 Training 時不斷強調測試代碼可讀性的重要性,這一篇就來簡單介紹 Flnent Asserentions 的安裝與使用,若有問題或是錯誤的地方歡迎各位高手給予指導。 … Read More
  • [UnitTest] Visual Studio 2017 按右鍵無法建立單元測試 ? 問題 最近心血來潮使用家中舊電腦小白寫 Code,在練習測試中發現竟然有點怪異,在要測試的 method 按下右鍵沒有 建立單元測試 Create Unit Test 選項,但相同練習專案拿到公司筆電就是正常的,經比對後發現舊筆電 Visual Studio 版本少安裝測試功能,以下簡單紀錄解決問題的過程 解決方案 在 Visual Studio 2017 早期版本這是已知問題,有開發者在 vs community 回報給開… Read More

0 意見:

張貼留言

Copyright © 2025 m@rcus 學習筆記 | Powered by Blogger

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com