きなこもち.net

.NET Framework × UiPath,Orchestrator × Azure × AWS × Angularなどの忘備録

IHttpClientFactory × Unit Test × テスト方法について

目的

HttpClientを使ったServiceのUnitTestの方法を調べる。

参考にしたサイト

テスト対象のクラス

テスト対象として、以下のクラスを利用する。このクラスは、Azure Maps Weather Serviceから指定されたロケーションの天気情報を取得することを目的としたサービスである。

UnitTestの追加

メソッドの追加

UnitTestを作成する時、Fact属性の追加やらメソッド名の命名規約やらでいちいち迷うことがある。それを少しでも解消するため、CodeSnippetを用意した。

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>XTest Method</Title>
      <Shortcut>xtestm</Shortcut>
      <Description>Code snippet of Test Method of XUnitTest</Description>
      <Author>Kinakomotitti</Author>
    </Header>
    <Snippet>
      <Imports>
        <Import>
          <Namespace>Microsoft.VisualStudio.TestTools.UnitTesting</Namespace>
        </Import>
      </Imports>
      <Declarations>
        <Literal>
          <ID>name</ID>
          <ToolTip>Replace your name of the test mehod.</ToolTip>
          <Default>PurposeOfTheTest_Conditions_ExpectedBehavior</Default>
        </Literal>
          <Literal Editable="false">
            <ID>XTestMethod</ID>
            <Function>SimpleTypeName(global::Microsoft.VisualStudio.TestTools.UnitTesting.XTestMethod)</Function>
          </Literal>
      </Declarations>
      <Code Language="csharp"><![CDATA[[Fact]
      public void $name$()
      {
            #region Arrange

            #endregion

            #region Action

            #endregion

            #region Assert

            #endregion
          $end$
      }]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

これを適当なファイルに設定し、コードスニペットマネージャーでインポートすると、xtestmと入力し、Tab二回押しすることでテストメソッドのひな形が実装できる。

テストの作成

テストを実施するため、テスト対象のクラスを生成する。今回のテスト対象は、ILogger 、IOptions 、 IHttpClientFactory の3つのインターフェースをInjectしているため、まずは、それらのMockを作成する。(これがArrangeリージョンで行うこと。)

#region Arrange
var loggerStub = new Mock<ILogger<AzureMaps>>();
var logger = loggerStub.Object;

var iOptionsStub = new AzureMaps();
var iOptions = Options.Create<AzureMaps>(iOptionsStub);

var httpMessageHandlerStub = new Mock<HttpMessageHandler>();
httpMessageHandlerStub.Protected().Setup<Task<HttpResponseMessage>>("SendAsync",
                ItExprIsAny<HttpRequesMessage>(),
                ItExprIsAny<CancellatinToken>())
    .ReturnsAsync(new HttpResponseMessage()
    {
        StatusCode = HttpStatusCode.OK,
        Content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(new CurrentConditionsModel()
        {
            results = new Result[] {
                (new Result()
                {
                    dateTime=DateTime.Now
                })
            }
        }))
    }) ;
var httpClientStub = new HttpClient(httpMessageHandlerStub.Object);
var httpClientFactoryStub = new Mock<IHttpClientFactory>();
httpClientFactoryStub.Setup(m => m.CreateClient(It.IsAny<string>()))
    .Returns(httpClientStub);
var httpClientFactory = httpClientFactoryStub.Object;
#end region

HttpMessageHandlerと、HttpResponseMessageのインスタンスを設定するところでコードが煩雑になりぱっと見わかりにくくなっている。これは、別メソッドに外だしするなどして可読性をよくする必要がありそう。

次に、テスト対象のサービスのメソッドを実行するコードを追加する。

 #region Action
 var targetService = new AzureMapsClientService(logger, iOptions, httpClientFactory);
 var actual = targetService.WeatherGetCurrentConditions("query");
 #endregion

最後に、検証を行う。

#region Assert
Assert.True(actual.requestStatus);
Assert.NotNull(actual.response);
#endregion

まとめ

IHttpClientFactoryを利用することで、実際にリクエストを出さないでUnitTetsを実施することができた。HttpResponseMessageの作成部分の処理を工夫すれば、メンテしやすいテストケースになり、テストもはかどりそう。

作ったコード

HttpMessageHandlerはこのクラス意外にもよく利用されることが想定されるため、BaseUnitTestクラスを新たに作成し、そちらに作成処理を移動させた。
UnitTestSampleのGitリポジトリ