web-dev-qa-db-ja.com

Moqを使用したHttpClientのモック

HttpClientを使用するクラスを単体テストするのが好きです。クラスコンストラクターにHttpClientオブジェクトを挿入しました。

public class ClassA : IClassA
{
    private readonly HttpClient _httpClient;

    public ClassA(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<HttpResponseMessage> SendRequest(SomeObject someObject)
    {
        //Do some stuff

        var request = new HttpRequestMessage(HttpMethod.Post, "http://some-domain.in");

        //Build the request

        var response = await _httpClient.SendAsync(request);

        return response;
    }
}

ここで、ClassA.SendRequestメソッドの単体テストを行います。ユニットテストフレームワークにはMs Testを、モッキングにはMoqを使用しています。

HttpClientをモックしようとすると、NotSupportedExceptionがスローされます。

[TestMethod]
public async Task SendRequestAsync_Test()
{
    var mockHttpClient = new Mock<HttpClient>();

    mockHttpClient.Setup(
        m => m.SendAsync(It.IsAny<HttpRequestMessage>()))
    .Returns(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
}

この問題をどのように解決できますか?

9
Amitava Karan

この特定のオーバーロードメソッドは仮想ではないため、Moqでオーバーライドできません。

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);

これがNotSupportedExceptionをスローする理由です

あなたが探している仮想メソッドはこのメソッドです

public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);

ただし、HttpClientのモックは、内部メッセージハンドラの場合ほど簡単ではありません。

リクエストを偽装するときに柔軟性を高めるカスタムメッセージハンドラースタブを備えた具象クライアントを使用することをお勧めします。

委任ハンドラスタブの例を次に示します。

public class DelegatingHandlerStub : DelegatingHandler {
    private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
    public DelegatingHandlerStub() {
        _handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
    }

    public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
        _handlerFunc = handlerFunc;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
        return _handlerFunc(request, cancellationToken);
    }
}

デフォルトのコンストラクターは基本的に、以前にモックしようとしていたことを実行していることに注意してください。また、リクエストのデリゲートを使用してカスタムシナリオを増やすこともできます。

スタブを使用すると、テストを次のようなものにリファクタリングできます

public async Task _SendRequestAsync_Test() {
    //Arrange           
    var handlerStub = new DelegatingHandlerStub();
    var client = new HttpClient(handlerStub);
    var sut = new ClassA(client);
    var obj = new SomeObject() {
        //Populate
    };

    //Act
    var response = await sut.SendRequest(obj);

    //Assert
    Assert.IsNotNull(response);
    Assert.IsTrue(response.IsSuccessStatusCode);
}
11
Nkosi

HttpClientを使用した適切なモックは、ほとんどの人がdotnetでユニットテストを行う前に作成されたため、大変な作業です。リクエストURLに一致するパターンに基づいて返信定型文を返すスタブHTTPサーバーを設定することもあります。つまり、実際のHTTPリクエストをモックではなくローカルホストサーバーにテストします。 WireMock.net を使用すると、これが本当に簡単になり、ユニットテストのニーズのほとんどを満たすのに十分な速度で実行されます。

したがって、http://some-domain.inの代わりに、いくつかのポートでlocalhostサーバーのセットアップを使用し、次のようにします。

var server = FluentMockServer.Start(/*server and port can be setup here*/);
server.Given(
      Request.Create()
      .WithPath("/").UsingPost()
   )
   .RespondWith(
       Response.Create()
       .WithStatusCode(200)
       .WithHeader("Content-Type", "application/json")
       .WithBody("{'attr':'value'}")
   );

テストでのワイアモックの使用に関する詳細と ガイダンスはここにあります

4
alastairtree