目的

面試的時候被問到要如何做包含外部api的單元測試問題,稍微查一下其實很簡單,怎麼當下答不出來呢? 主要有兩種方式,一種為.net core 2.1以後有提供IHttpClientFactory的介面可以使用。

建立新專案

選擇ASP.NET Core Web API專案範本,並執行下一步 步驟一

設定新的專案

命名你的專案名稱,並選擇專案要存放的位置。 步驟二

其他資訊

直接進行下一步 步驟三

建立資料夾

步驟四

編輯Program.cs檔案

註冊AddHttpClient。

builder.Services.AddHttpClient();

步驟五

新增一個類別檔

  • 加入前 步驟6-1
  • 加入後 步驟6-2

建構子注入

將註冊的httpclient透過建構子注入

    readonly IHttpClientFactory _httpClientFactory;
    public CallAPIServices(IHttpClientFactory httpClientFactory) {
      _httpClientFactory = httpClientFactory;
    }
  • 加入前 步驟7-1
  • 加入後 步驟7-2

新增方法

新增一個會去發外部請求的方法,模擬當有包含第三方api時如何測試

    public async Task<string> Get() {
      var client = _httpClientFactory.CreateClient();
      var response = await client.GetAsync("https://example.com");
      if (response.IsSuccessStatusCode) {
        var responseString = await response.Content.ReadAsStringAsync();
        return responseString;
      }
      return "";
    }

步驟8

新增測試專案

對方案點選右鍵>加入>新增專案

步驟9

設定新的專案

替測試專案命名,建議命名規則以.Tests做結尾 步驟10

其他資訊

專案架構需要與要測試的專案相同 步驟11

加入參考

將要測試的專案加入測試專案

對CallAPIServices.Tests點選右鍵>加入>專案參考

步驟12

NuGet加入套件

透過NuGet安裝Moq套件至測試專案 步驟13

新增對應資料夾與測試檔案

新增檔案,檔名需要與要測試的檔案一致,並加上Tests 步驟14

新增程式碼

在新增的檔案內新增一個方法並寫入程式碼,一開始會看到許多紅色波浪,是因為沒有加入參考

    //定義這個方法是不需要傳入參數的單元測試,另一種為[Theory],詳細說明可看相關參考5
    [Fact]
    //命名規則 => 類別名稱_方法名稱_要測試的方法行為_回傳狀態
    public async Task CallAPIServices_Get_HttpClient_Success() {
      //模擬出一個IHttpClientFactory的假物件
      var mockFactory = new Mock<IHttpClientFactory>();
      //產生http回傳的實例
      HttpResponseMessage result = new HttpResponseMessage() {
        StatusCode = HttpStatusCode.OK,
        Content = new StringContent("{'account':'bill','age':18}")
      };
      //模擬出一個http處理程序的基本類型實例的假物件
      var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
      //模擬http發出請求後所做的一連串事情,最後並回傳結果
      //protected => 發出請求的方法SendAsync是一個protected存取修飾詞,不能直接存取,所以告訴moq請幫忙執行protected方法,詳細說明可看參考6
      //Setup => 設定此執行動作為SendAsync方法,並需要兩個傳入參數,HttpRequestMessage與CancellationToken
      //ItExpr.IsAny => 請moq協助傳入一個參數為HttpRequestMessage與CancellationToken的假參數,另一種為It.IsAny是非protected方法使用
      //ReturnsAsync => 回傳非同步的值
      mockHttpMessageHandler.Protected()
          .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
          .ReturnsAsync(result);
      //將製作好的假請求與回傳給HttpClient
      var client = new HttpClient(mockHttpMessageHandler.Object);
      //模擬實際請求,並接收到我們所製作的假物件
      mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
      //將製作好的完整請求流程給我們在使用的方法
      var services = new HttpclientExample.Services.CallAPIServices(mockFactory.Object);
      //執行要測試的方法
      var response = await services.Get();
      //回傳值不能為null
      Assert.NotNull(response);
      //回傳值必須一致
      Assert.Equal("{'account':'bill','age':18}", response);
    }
  • 加入前 步驟15-1

步驟15-2

  • 加入後 步驟15-3

步驟15-4

執行測試

點右鍵執行測試,最後結果會如左下角顯示成功 步驟16

相關參考

單元測試好的做法 為什麼要使用IHttpClientFactory moq套件介紹 單元測試介紹 Fact跟Theory差別 存取修飾詞

範例檔

GitHub