2024-10-23 013_學寫API_單元測試_MOCK

2024-10-23 013_學寫API_單元測試_MOCK

Mock 是單元測試中經常使用的一種技術,用來模擬(即 “mock”)應用程序中的外部依賴項。它使得我們可以隔離測試的對象,而不需要實際連接到外部系統,如資料庫、網路服務,或者其他無法在單元測試中方便訪問的資源。Mocking 允許測試代碼在沒有這些外部依賴的情況下進行驗證,從而專注於測試特定功能的行為。

為什麼要使用 Mock?

在實際的應用中,API 控制器可能依賴於許多外部資源來完成其工作。例如:

  • 資料庫:要從資料庫中讀取和寫入數據。
  • 外部服務:與其他微服務或第三方系統進行互動。
  • 文件系統:讀取和寫入文件。

在單元測試中,如果我們依賴這些外部資源來進行測試,會有以下幾個問題:

  1. 不可預測性:外部資源(如資料庫或網路服務)可能會有不可預測的變動,例如數據更改或者網路連線中斷,這會導致測試結果的不穩定。
  2. 性能問題:訪問外部資源的操作可能非常耗時,使得單元測試變得慢而不易維護。
  3. 隔離性差:單元測試的目的是要檢查單一方法或單一模組的行為,因此應該將這些外部依賴隔離,保證測試專注於該模組內部的邏輯。

使用 Mock 可以解決以上問題,因為 Mock 是在測試中用來模擬這些外部依賴的假實現,允許測試執行在受控的環境中,且結果是可預測的。

Mock 是如何使用的?

Mock 是通過創建依賴項的假版本來替代實際的依賴項。例如,如果您的控制器需要從資料庫中讀取包裹的信息,在單元測試中您可以使用 Mock 來返回假數據,而不是從資料庫實際查詢。

以下是如何使用 Mock 的一些關鍵概念:

  1. 模擬外部依賴項

    • 使用 Mock 的方式可以創建控制器所依賴的服務的假實例。
    • 例如,在測試 PackageController 時,如果控制器依賴一個資料庫服務 IPackageService,我們可以創建 IPackageService 的一個 Mock 版本。
  2. 控制輸入和輸出

    • 使用 Mock 時,我們可以明確地定義方法應該返回什麼值,以便在測試中控制不同的情景。
    • 例如,當呼叫 GetPackageByTrackingNumber("12345") 時,可以讓 Mock 返回一個特定的包裹物件。
  3. 驗證交互

    • Mock 也可以用來驗證測試代碼是否正確地與外部依賴互動。例如,我們可以檢查 CreatePackage() 方法是否正確地呼叫了一次資料庫的 Save() 方法。

Mock 框架

在 C# 中,我們可以使用一些流行的 Mock 框架來輔助完成這些 Mock 操作,例如:

  • Moq:一個非常流行且簡單易用的 Mock 框架。
  • NSubstitute:另一個強大且簡單的 Mock 框架。

Moq 為例,假設 PackageController 依賴於一個服務 IPackageService 來進行包裹的資料處理。我們可以用 Moq 來模擬這個依賴項:

  1. 安裝 Moq

    • 您可以通過 NuGet 安裝 Moq 套件:
      1
      Install-Package Moq
  2. 使用 Moq 創建 Mock 依賴項

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    using Moq;
    using LogisticsAPI.Controllers;
    using LogisticsAPI.Models;
    using Microsoft.Extensions.Logging;
    using Xunit;

    public class PackageControllerTests
    {
    private readonly PackageController _controller;
    private readonly Mock<IPackageService> _mockPackageService;
    private readonly ILogger<PackageController> _logger;

    public PackageControllerTests()
    {
    _mockPackageService = new Mock<IPackageService>();
    _logger = new LoggerFactory().CreateLogger<PackageController>();

    // 初始化控制器,並使用 Mock 版本的 IPackageService
    _controller = new PackageController(_logger, _mockPackageService.Object);
    }

    [Fact]
    public void GetPackageByTrackingNumber_ValidNumber_ReturnsPackage()
    {
    // Arrange: 設定 mock 依賴,當查詢 tracking number "12345" 時,返回一個假包裹
    var package = new Package { TrackingNumber = "12345", Sender = "Alice", Recipient = "Bob", Status = "待派送" };
    _mockPackageService.Setup(service => service.GetPackageByTrackingNumber("12345")).Returns(package);

    // Act: 呼叫控制器的方法
    var result = _controller.GetPackageByTrackingNumber("12345") as OkObjectResult;

    // Assert: 驗證結果是否正確
    Assert.NotNull(result);
    Assert.IsType<Package>(result.Value);
    Assert.Equal("12345", ((Package)result.Value).TrackingNumber);
    }
    }
    • 解釋
      • **Mock<IPackageService>**:創建了一個 IPackageService 的 Mock 版本。
      • **Setup()**:設定當 Mock 的方法被呼叫時的行為,例如當呼叫 GetPackageByTrackingNumber("12345"),我們希望返回一個包裹。
      • **Returns()**:指定當條件滿足時應返回的假數據。

Mock 的優勢

  • 測試隔離性:您可以在沒有資料庫、沒有網路等外部依賴的情況下進行測試,這樣每個測試都是獨立的,不受外部環境影響。
  • 可控性:使用 Mock 可以完全控制測試中不同依賴的行為。例如,當測試異常情況時,可以輕鬆地模擬依賴項拋出例外。
  • 驗證交互:Mock 也能幫助您檢查是否與外部依賴進行了正確的交互,例如方法是否被呼叫、呼叫的次數等。

小結

Mock 是單元測試中非常重要的工具,尤其在面對有外部依賴的情況下,它允許開發者通過模擬這些依賴來進行獨立的測試,從而確保測試能夠在受控環境下進行,且能正確驗證應用程式的邏輯。希望這些解釋能幫助您理解什麼是 Mock 以及它的作用!