HttpClient を Moq でモック化する

厳密には、HttpClient が内部で最終的に呼び出す HttpMessageHandler を、Moq を使ってモック化する。

まずは HttpMessageHandler のインタフェースを、存在しないのででっち上げる。

public interface IHttpMessageHandler
{
    Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
}

HttpMessageHandler.SendAsync は protected internal なので、一筋縄ではいかない。Moq が提供している protected なメンバーをモック化する機能を使う。

var handlerMock = new Mock<HttpMessageHandler>();
handlerMock.Protected()
    .As<IHttpMessageHandler>()
    .Setup(m => m.SendAsync(
        It.Is<HttpRequestMessage>(r =>
            r.RequestUri.PathAndQuery.Contains("/api/account/login") &&
            r.Method == HttpMethod.Post &&
            r.Content.ReadAsStringAsync().Result.Contains("foobar") &&
            r.Content.ReadAsStringAsync().Result.Contains("P@ssw0rd")
        ),
        It.IsAny<CancellationToken>()))
    .Returns(() =>
    {
        var response = new HttpResponseMessage(HttpStatusCode.BadRequest);
        return Task.FromResult(response);
    });

ついでに IHttpClientFactory もモック化しておく。こちらは素直。

var factoryMock = new Mock<IHttpClientFactory>();
factoryMock.Setup(m => m.CreateClient(It.IsAny<string>()))
    .Returns(() =>
    {
        return new HttpClient(handlerMock.Object, false);
    });

HttpMessageHandler を継承したテスト用クラスを定義するのが簡単だけど、他は Moq 使っているのに、HttpClient 関連だけ違うっていうのは気持ち悪いので、なんとかやってみた。