C#でHttpClientをモックしてテストする
2019-12-16
azblob://2022/11/11/eyecatch/2019-12-16-how-to-mock-httpclient-000.png

これはFIXER Advent Calendar 2019 の16日目の記事です。前日は金谷さんのDigital Twinの世界でした。

Fintech Division所属で自称.NET Developerの和田です。今回は.NET FrameworkでHttpClientのテストダブルを生成してくれるRichardSzalay.MockHttpというライブラリをご紹介します。

その前にちょっと謝罪を……。昨日こんなツイートをしたんですよ。

今朝こんな通知が来てですね……。

すみません!今回はできませんでした!後で別に記事書くので許してください!

概要

閑話休題。正直.NETでテストダブルを生成するときにはMoqを使いたいんですが、.NET FrameworkのHttpClientはインターフェイスが定義されていないのでそう簡単には扱わせてくれません。自力でモックするにはHttpClientが依存しているIHttpMessageHandlerをモックしてから、HttpClientのコンストラクタに渡す必要があってちょっと面倒です。

RichardSzalay.MockHttpはそのあたりの実装をいい感じにラップしてくれます。

[TestMethod]
public async Task MockHttpExample()
{
    var mockHttp = new MockHttpMessageHandler();

    mockHttp
        .When("http://dst.test/")    // http://dst.test/にリクエストを送ったとき
        .Respond(HttpStatusCode.OK); // 200 OKを返す

    var client = mockHttp.ToHttpClient();

    var res = await client.GetAsync("http://dst.test");

    Assert.IsTrue(res.IsSuccessStatusCode);
}

架空のURLにGETリクエストを投げてもTrueになりましたね。

使い方

Nugetで配布されているのでRichardSzalay.MockHttpパッケージをインストールしてください。

PM> Install-Package RichardSzalay.MockHttp

テストダブルで受ける宛先(HTTPメソッドとURL)の設定の仕方にはWhenとExpectの2通りあります。

When

HttpClientのスタブを作成したい時に使います。Whenで定義したルールには複数回マッチさせることができます。マッチしなかったリクエストに対しては、Fallbackで指定された動作を行います。

Expect

HttpClientのモックを作成したい時に使います。Expectで定義したルールは、1回ずつ、定義した順にマッチします。最後に mockHttp.VerifyNoOutstandingExpectation() メソッドを呼ぶことで、全ての期待したリクエストが来たかの確認ができます。

[TestMethod]
public async Task ExpectExample()
{
    var mockHttp = new MockHttpMessageHandler();

    mockHttp.Expect("http://dst.test/")
        .Respond(HttpStatusCode.OK);

    mockHttp.Expect("http://dst.test/value/")
        .Respond("application/json", "{'name':'cloudconfig','value':'tech-blog'}");

    var client = mockHttp.ToHttpClient();

    var res1 = await client.GetAsync("http://dst.test/");
    var res2 = await client.GetAsync("http://dst.test/value/");

    Assert.IsTrue(res1.IsSuccessStatusCode);
    Assert.IsTrue(res2.IsSuccessStatusCode);
    mockHttp.VerifyNoOutstandingExpectation();
}

一定の手順を踏んで利用するタイプの外部APIに接続するメソッドのテストなどで利用できますね。

Matchers (Withから始まるメソッド)

WithQueryStringなど、Withから始まるメソッドはMatcherと呼び、期待するリクエストの内容(クエリーやヘッダーなど)を定義するのに使います。WhenやExpectの後ろに繋げて呼び出します。

[TestMethod]
public async Task MatcherExample()
{
    var mockHttp = new MockHttpMessageHandler();

    mockHttp.When("http://dst.test/")
        .WithQueryString("name", "cloudconfig")
        .Respond(HttpStatusCode.OK);

    var client = mockHttp.ToHttpClient();

    var res = await client.GetAsync("http://dst.test/?name=cloudconfig");

    Assert.IsTrue(res.IsSuccessStatusCode);
}

終わりに

HttpClientはIDisposeを実装しているのにusing句で使うとポートを食いつぶすという罠があったりして使いにくいので、個人的にはRestSharpを使いたいです。こちらはインターフェースも提供されているのでテストを行う上でも使いやすいですし。クライアントをstaticで使いまわす必要もないので楽です。

それでも、どうしてもHttpClientを使ったコードのテストを書かなければいけない人(e.g. 昔の僕)の助けになれば幸いです。Richard Szalayさん、便利なライブラリをありがとうございました。

明日は前田君の 「 NestJSのお話」です。NestJS、詳しくは知らないですがサーバーレスでBFF動かすのにいい感じって聞きました。どんな話なのかこうご期待!ですね。それでは。