BEACHSIDE BLOG

MicrosoftとかC#を好むレンジャーの個人的メモ

C# HttpClient の Mock でFake のレスポンスを返す in 単体テスト

単体テストでHttpClient のMock的なので Fakeなレスポンスを返す時の方法のメモ(..というか聞かれたので答えた内容書いたってお話です。)

今回の単体テストする対象クラスは以下の想定です。

  • とあるビジネスロジックがあるクラスの中に HttpClient がいて、それ使って外部のWebAPIを呼んでる
  • 単体テストだから外部のWebAPIのレスポンスなんて正しいものが来る前提でテストするよね

環境とか...

前提知識 - その1

本題とは全然関係ないですが、.Net Framework の HttpClient 使うなら知っておいてほしい件...

開発者を苦しめる.NETのHttpClientのバグと紛らわしいドキュメント

前提知識 - その2

HttpClient でよく使いそうな PostAsync メソッドや GetAsync メソッドは、HttpClient 内部では最終的に SendAsync メソッドをコールしています(私の知ってる限り...あってるはず....)。

例えばPostAsync メソッドがOrverrideできないないからFakeのレスポンスを返せないってことはなく、最終的にコールされる SendAsync でFakeのレスポンスを返してあげるだけで今回やりたいことは実現できます(何をもって成立するかはケースバイケースですが、ここではFakeのレスポンスを返せるという意味で成立するってことにしてます)

テスト対象のクラスの実装

テストをしたいクラスでは、テスタビリティをあげるため Mockにしたい部分をDIできるようにしてあげることが多いでしょうか。今回だと HttpClientコンストラクターでDIできるような作りにしてあげます。ということでHttpClientDemoというクラスをつくりました。

RunDemoMethod メソッド(15行目)の中で、引数のURLに対してHTTPのGETでレスポンスを取得し、レスポンスのHTTPの StatusCode を文字列で返すというシンプルな実装です。

単体テストプロジェクトのクラス実装

ここからは単体テストプロジェクトの話になりますので、MSTestのプロジェクトを適当に作っておきます。

Fakeのレスポンスを返すためのクラスの実装

「すべてのメソッドはSendAsyncに通ず」(いや違うけど)なので、SendAsync で指定のレスポンスを返せるようする DelegatingHandler を継承した FakeResponseHandler クラスを作りました。

さっと解説ですが、Visual Studioでwarningでて放置なのは嫌いなので、4行目でasync で await がないときのwarningを無効にしてます。

本題の SendAsync メソッド(18~21行目)をoverrideして、コンストラクター(12~15行目)で受け取った HttpResponseMessage を返してあげるだけ。うーん簡単。

単体テストクラスの実装

今回はFakeのレスポンスが想定通りに返るのを見るだけなので無意味なテストメソッド感が溢れていますが、これで想定のレスポンスが返ってくることが確認できます。

実装はみたまんまのシンプルなものですが、HttpClient を生成する際に、コンストラクターでhandlerを渡してあげると、今回想定してる動きをしてくれます。

上の実装は、StatusCodeだけをFakeな値にしてます。レスポンスのContentもFakeな値を返したいのであれば、20行目のところでContentを追加してあげることもできます。文字列のContentを入れるならこんな感じ。

var fakeResponse = new HttpResponseMessage(extected)
{
    Content = new StringContent("hello fake content")
};

もうちょっと拡張...

用途に合わせてどうにでも拡張すればよいですが、その一例として、URLに応じてレスポンスを変えるパターンの実装をしてみます。先ほど作ったFakeResponseHandlerのバージョンアップ版としてFakeResponseHandler2 (クラス名ダサい...)を作ってみます。

実装はみたままですね。(今回の用途用に)使いやすいように拡張メソッドを用意してます。

そして、先ほど作った単体テストクラスに以下のメソッドとかを追加してみました♪

それぞれ期待どおりに動くことが確認できます。


.NET Core ではGitHubリポジトリで実装が見れますが、基本的な動作はだいたい一緒な感じですね。

おしまい。