Runtime: 単体テスト用にhttpClient.GetAsync(..)メソッドをモックする簡単な方法はありますか?

作成日 2015年05月04日  ·  157コメント  ·  ソース: dotnet/runtime

System.Net.Httpがリポジトリにアップロードされました:smile :: tada :: balloon:

あるサービスでこれを使用したときはいつでも、それはうまく機能しますが、単体テストを困難にします=>私の単体テストは実際にその実際のエンドポイントに到達することを望んでいません。

何年も前に、私は@davidfowlに何をすべきか尋ねました。 私は言い換えて、彼を誤って引用しないことを望んでいます-しかし、彼は私がメッセージハンドラー(すなわちHttpClientHandler )を偽造し、それを配線する必要があることを提案しました。

そのため、単体テストの実行を支援するHttpClient.Helpersというヘルパーライブラリを作成することになりました。

したがって、これは機能します...しかし、それは_非常に_乱雑で..複雑に感じます。 ユニットテストが外部サービスへの実際の呼び出しを行わないことを確認する必要があるのは、私が最初ではないと確信しています。

もっと簡単な方法はありますか? ..のように、 IHttpClientインターフェイスだけを使用して、それをサービスに注入することはできませんか?

Design Discussion area-System.Net.Http test enhancement

最も参考になるコメント

こんにちは@ SidharthNabar-私の問題を読んでいただきありがとうございます、私も本当に感謝しています。

新しいHandlerクラスを作成します

あなたは私の質問に答えました:)

これも小刻みに動く大きなダンスです。私の実際のコードに_ネットワークにヒットしない_ように頼むだけです。

テストを簡単にするために、 HttpClient.Helpersリポジトリとnugetパッケージも作成しました。 ハッピーパスやネットワークエンドポイントによってスローされた例外のようなシナリオ...

それが問題です->これをすべて行うことはできません...代わりにメソッドをモックするだけですか?

コードで説明してみます。

目標:インターウェブから何かをダウンロードします。

public async Foo GetSomethingFromTheInternetAsync()
{
    ....
    using (var httpClient = new HttpClient())
    {
        html = await httpClient.GetStringAsync("http://www.google.com.au");
    }
    ....
}

2つの例を見てみましょう。

与えられたインターフェース(存在する場合):

public interface IHttpClient
{
    Task<string> GetStringAsync(string requestUri);    
}

私のコードは次のようになります...

public class SomeService(IHttpClient httpClient = null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _httpClient ?? new HttpClient()) // <-- CHANGE dotnet/corefx#1
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

とテストクラス

public async Task GivenAValidEndPoint_GetSomethingFromTheInternetAsync_ReturnsSomeData()
{
    // Create the Mock : FakeItEasy > Moq.
    var httpClient = A.Fake<IHttpClient>();

    // Define what the mock returns.
    A.CallTo(()=>httpClient.GetStringAsync("http://www.google.com.au")).Returns("some html here");

    // Inject the mock.
    var service = new SomeService(httpClient);
    ...
}

わーい! 終わり。

さて、現在の方法で...

  1. 新しいHandlerクラスを作成します-はい、クラスです!
  2. ハンドラーをサービスに注入します
public class SomeService(IHttpClient httpClient = null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _handler == null 
                                    ? new HttpClient()
                                    : new HttpClient(handler))
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

しかし、今は授業をしなければならないのが苦痛です。 あなたは今私を傷つけました。

public class FakeHttpMessageHandler : HttpClientHandler
{
 ...
}

そして、このクラスは非常に単純なものから始まります。 私が複数のGetAsync callsを持っているときまで(つまり、複数のHandlerインスタンスを提供する必要がありますか?)-または-単一のサービスに複数のhttpClientsがあります。 また、ハンドラーのDisposeを使用しますか、それとも同じロジックブロック(ctorオプション)で複数の呼び出しを実行している場合は再利用しますか?

例えば。

public async Foo GetSomethingFromTheInternetAsync()
{
    string[] results;
    using (var httpClient = new HttpClient())
    {
        var task1 = httpClient.GetStringAsync("http://www.google.com.au");
        var task2 = httpClient.GetStringAsync("http://www.microsoft.com.au");

        results = Task.WhenAll(task1, task2).Result;
    }
    ....
}

これにより、これを非常に簡単にすることができます。

var httpClient = A.Fake<IHttpClient>();
A.CallTo(() = >httpClient.GetStringAsync("http://www.google.com.au"))
    .Returns("gooz was here");
A.CallTo(() = >httpClient.GetStringAsync("http://www.microsoft.com.au"))
    .Returns("ms was here");

クリン、クリン、クリン、クリン:)

次に-次のビットがあります:発見可能

私が最初にMS.Net.Http.HttpClientを使い始めたとき、APIは非常に明白でした:+1:わかりました-文字列を取得して非同期で実行します..単純...

...しかしそれから私はテストをしました....そして今私はHttpClientHandlersについて学ぶことになっていますか? なんで? これはすべて隠蔽されるべきであり、この実装の詳細について心配する必要はないと思います。 多すぎる! あなたは私が箱の中を見て、配管のいくつかを学ぶべきだと言っています...それは痛いです:cry:

それはすべて、これを本来よりも複雑にしている、IMO。


マイクロソフトを助けてください-あなたは私の唯一の希望です。

全てのコメント157件

HttpClientは、別のHTTPライブラリの抽象化レイヤーにすぎません。 その主な目的の1つは、決定論的な単体テストを作成する機能を含むがこれに限定されない動作(実装)を置き換えることを可能にすることです。

他の作品では、 HttpClient自体が「実際の」オブジェクトと「モック」オブジェクトの両方として機能し、コードのニーズを満たすためにHttpMessageHandlerを選択します。

しかし、メッセージハンドラーをセットアップするために非常に多くの作業が必要であると感じた場合(そのように感じた場合)、これを優れたインターフェイスで処理できますか? 私は解決策を完全に誤解していますか?

@ PureKrome-これを議論のために持ち出してくれてありがとう。 「メッセージハンドラの設定には大変な作業が必要」とはどういう意味ですか?

ネットワークにアクセスせずにHttpClientを単体テストする1つの方法は、次のとおりです。

  1. HttpMessageHandlerから派生する新しいHandlerクラス(FooHandlerなど)を作成します
  2. 要件に応じてSendAsyncメソッドを実装します。ネットワークにアクセスしたり、要求/応答などをログに記録したりしないでください。
  3. このFooHandlerのインスタンスをHttpClientのコンストラクターに渡します。
    var handler = new FooHandler();
    var client = new HttpClient(handler);

HttpClientオブジェクトは、組み込みのHttpClientHandlerの代わりにHandlerを使用します。

ありがとう、
シド

こんにちは@ SidharthNabar-私の問題を読んでいただきありがとうございます、私も本当に感謝しています。

新しいHandlerクラスを作成します

あなたは私の質問に答えました:)

これも小刻みに動く大きなダンスです。私の実際のコードに_ネットワークにヒットしない_ように頼むだけです。

テストを簡単にするために、 HttpClient.Helpersリポジトリとnugetパッケージも作成しました。 ハッピーパスやネットワークエンドポイントによってスローされた例外のようなシナリオ...

それが問題です->これをすべて行うことはできません...代わりにメソッドをモックするだけですか?

コードで説明してみます。

目標:インターウェブから何かをダウンロードします。

public async Foo GetSomethingFromTheInternetAsync()
{
    ....
    using (var httpClient = new HttpClient())
    {
        html = await httpClient.GetStringAsync("http://www.google.com.au");
    }
    ....
}

2つの例を見てみましょう。

与えられたインターフェース(存在する場合):

public interface IHttpClient
{
    Task<string> GetStringAsync(string requestUri);    
}

私のコードは次のようになります...

public class SomeService(IHttpClient httpClient = null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _httpClient ?? new HttpClient()) // <-- CHANGE dotnet/corefx#1
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

とテストクラス

public async Task GivenAValidEndPoint_GetSomethingFromTheInternetAsync_ReturnsSomeData()
{
    // Create the Mock : FakeItEasy > Moq.
    var httpClient = A.Fake<IHttpClient>();

    // Define what the mock returns.
    A.CallTo(()=>httpClient.GetStringAsync("http://www.google.com.au")).Returns("some html here");

    // Inject the mock.
    var service = new SomeService(httpClient);
    ...
}

わーい! 終わり。

さて、現在の方法で...

  1. 新しいHandlerクラスを作成します-はい、クラスです!
  2. ハンドラーをサービスに注入します
public class SomeService(IHttpClient httpClient = null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _handler == null 
                                    ? new HttpClient()
                                    : new HttpClient(handler))
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

しかし、今は授業をしなければならないのが苦痛です。 あなたは今私を傷つけました。

public class FakeHttpMessageHandler : HttpClientHandler
{
 ...
}

そして、このクラスは非常に単純なものから始まります。 私が複数のGetAsync callsを持っているときまで(つまり、複数のHandlerインスタンスを提供する必要がありますか?)-または-単一のサービスに複数のhttpClientsがあります。 また、ハンドラーのDisposeを使用しますか、それとも同じロジックブロック(ctorオプション)で複数の呼び出しを実行している場合は再利用しますか?

例えば。

public async Foo GetSomethingFromTheInternetAsync()
{
    string[] results;
    using (var httpClient = new HttpClient())
    {
        var task1 = httpClient.GetStringAsync("http://www.google.com.au");
        var task2 = httpClient.GetStringAsync("http://www.microsoft.com.au");

        results = Task.WhenAll(task1, task2).Result;
    }
    ....
}

これにより、これを非常に簡単にすることができます。

var httpClient = A.Fake<IHttpClient>();
A.CallTo(() = >httpClient.GetStringAsync("http://www.google.com.au"))
    .Returns("gooz was here");
A.CallTo(() = >httpClient.GetStringAsync("http://www.microsoft.com.au"))
    .Returns("ms was here");

クリン、クリン、クリン、クリン:)

次に-次のビットがあります:発見可能

私が最初にMS.Net.Http.HttpClientを使い始めたとき、APIは非常に明白でした:+1:わかりました-文字列を取得して非同期で実行します..単純...

...しかしそれから私はテストをしました....そして今私はHttpClientHandlersについて学ぶことになっていますか? なんで? これはすべて隠蔽されるべきであり、この実装の詳細について心配する必要はないと思います。 多すぎる! あなたは私が箱の中を見て、配管のいくつかを学ぶべきだと言っています...それは痛いです:cry:

それはすべて、これを本来よりも複雑にしている、IMO。


マイクロソフトを助けてください-あなたは私の唯一の希望です。

私も、HttpClientを使用するさまざまなものをテストするための簡単でシンプルな方法を見たいと思っています。 :+1:

詳細な応答とコードスニペットをありがとう-それは本当に役立ちます。

まず、各リクエストを送信するためにHttpClientの新しいインスタンスを作成していることに気付きました。これはHttpClientの意図されたデザインパターンではありません。 1つのHttpClientインスタンスを作成し、それをすべてのリクエストに再利用すると、接続プールとメモリ管理を最適化するのに役立ちます。 HttpClientの単一インスタンスを再利用することを検討してください。 これを行うと、偽のハンドラーをHttpClientの1つのインスタンスに挿入するだけで完了です。

第二に-あなたは正しいです。 HttpClientのAPI設計は、テスト用の純粋なインターフェイスベースのメカニズムには適していません。 将来的には、静的メソッド/ファクトリパターンを追加して、そのファクトリから、またはそのメソッドの後に作成されたすべてのHttpClientインスタンスの動作を変更できるようにすることを検討しています。 ただし、このための設計はまだ決まっていません。 ただし、重要な問題は残ります。偽のハンドラーを定義して、HttpClientオブジェクトの下に挿入する必要があります。

@ ericstj-これについての考えは?

ありがとう、
シド。

@SidharthNabarなぜあなた/チームはこのクラスのインターフェースを提供することに躊躇しているのですか? 開発者がハンドラーについて_学習_し、次に偽のハンドラークラスを_作成_しなければならないという全体的なポイントが、インターフェイスの必要性を正当化する(または少なくとも強調する)のに十分な問題点であることを望んでいましたか?

ええ、なぜインターフェースが悪いことになるのかわかりません。 これにより、HttpClientのテストが非常に簡単になります。

フレームワークでインターフェースを避ける主な理由の1つは、バージョンが適切でないことです。 必要があればいつでも自分で作成でき、フレームワークの次のバージョンで新しいメンバーを追加する必要があるときに壊れることはありません。 @KrzysztofCwalinaまたは@terrajobstは、この設計ガイドラインの履歴/詳細をより多く共有できる可能性があります。 この特定の問題は、宗教的な議論に近いものです。私はあまりにも実用的すぎて、それに参加することはできません。

HttpClientには、ユニットの妥当性に関する多くのオプションがあります。 封印されておらず、そのメンバーのほとんどは仮想です。 @sharwellが指摘しているように、抽象化を単純化するために使用できる基本クラスと、HttpMessageHandlerで慎重に設計された拡張ポイントがあります。 IMO @HenrikFrystykNielsenのおかげで、非常によく設計されたAPIです。

:+1:インターフェース

:wave: @ericstj :+1 :: cake:にジャンプしてくれてありがとうヒープ

フレームワークでインターフェースを避ける主な理由の1つは、バージョンが適切でないことです。

うん-素晴らしいポイント。

この特定の問題は、宗教的な議論に近いものです

ええ...その点はよく理解されています。

HttpClientには、ユニットの妥当性に関する多くのオプションがあります

おー? 私はこれに苦労しています:blush:したがってこの問題の理由:blush:

封印されておらず、そのメンバーのほとんどは仮想です。

メンバーは仮想ですか? おやおや、私はそれを完全に逃した! それらが仮想である場合、モックフレームワークはそれらのメンバーをモックアウトすることができます:+1:そして私たちはインターフェースを必要としません!

HttpClient.csを見てみましょう...

public Task<HttpResponseMessage> GetAsync(string requestUri) { .. }
public Task<HttpResponseMessage> GetAsync(Uri requestUri) { .. }

うーん。 それらは仮想ではありません...ファイルを検索しましょう...えーと.. virtualキーワードが見つかりません。 つまり、_other_メンバーから_otherrelated_クラスは仮想であるということですか? もしそうなら..それから私は私が提起している問題に戻っています-私たちは今GetAsyncが何をしているのかを確認するために内部を見る必要がありますので、次に何を作成/配線/などするかを知っています...

私は本当に基本的なことを理解していないと思います、ここで¯(°_°)/¯?

編集:多分それらのメソッドは仮想である可能性がありますか? PRできます!

SendAsyncは仮想であり、その上にある他のほとんどすべてのAPIレイヤーです。 「ほとんど」は間違っていました、私の記憶はそこで私に間違って役立ちます。 私の印象では、ほとんどが仮想メンバーの上に構築されているため、事実上仮想でした。 通常、カスケードオーバーロードの場合、仮想化は行いません。 仮想ではないSendAsyncのより具体的な過負荷があり、修正される可能性があります。

ああ! Gotcha:blush:つまり、これらのメソッドはすべてSendAsync ..を呼び出すことになり、これはすべての面倒な作業を行います。 したがって、これはまだここに発見可能性の問題があることを意味します..しかしそれを脇に置いておきましょう(それは意見です)。

SendAsyncをどのようにモックしてこの基本的なサンプルを提供しますか...
Install-Package Microsoft.Net.Http
Install-Package xUnit

public class SomeService
{
    public async Task<string> GetSomeData(string url)
    {
        string result;
        using (var httpClient = new HttpClient())
        {
            result = await httpClient.GetStringAsync(url);
        }

        return result;
    }
}

public class Facts
{
    [Fact]
    public async Task GivenAValidUrl_GetSomeData_ReturnsSomeData()
    {
        // Arrange.
        var service = new SomeService();

        // Act.
        var result = await service.GetSomeData("http://www.google.com");

        // Assert.
        Assert.True(result.Length > 0);
    }
}

フレームワークでインターフェースを避ける主な理由の1つは、バージョンが適切でないことです。 必要があればいつでも自分で作成でき、フレームワークの次のバージョンで新しいメンバーを追加する必要があるときに壊れることはありません。

この考え方は、完全な.NETFrameworkで完全に理解できます。

ただし、.NET Coreは多くの小さなパッケージに分割されており、それぞれが個別にバージョン管理されています。 セマンティックバージョニングを使用し、重大な変更があったときにメジャーバージョンをバンプすれば、完了です。 影響を受けるのは、パッケージを新しいメジャーバージョンに明示的に更新する人だけです。これは、重大な変更が文書化されていることを知っています。

私はあなたが毎日すべてのインターフェースを壊すべきだと主張しているのではありません。壊れるような変更は理想的には新しいメジャーバージョンにまとめられるべきです。

将来の互換性の理由でAPIを機能不全にするのは悲しいことです。 .NET Coreの目標の1つは、すでにインストールされている何千ものアプリケーションを壊す微妙な.NET更新についてもう心配する必要がないため、より高速に反復することではありませんでしたか?

私の2セント。

@MrJul :+1:
バージョン管理は問題にはなりません。 それがバージョン番号が存在する理由です:)

バージョン管理はまだ非常に問題です。 インターフェイスにメンバーを追加することは、重大な変更です。 デスクトップの受信トレイであるこのようなコアライブラリの場合、将来のバージョンで機能をデスクトップに戻したいと考えています。 私たちがフォークした場合、それは人々が両方の場所で実行されるポータブルコードを書くことができないことを意味します。 重大な変更の詳細については、 https://github.com/dotnet/corefx/wiki/Breaking-Changesを参照してください。

これは良い議論だと思いますが、前に述べたように、インターフェースと抽象クラスの議論にはあまり傷がありません。 おそらく、これは次の設計会議に持ち込むトピックですか?

私はあなたが使用しているテストライブラリにあまり精通していませんが、私がすることは、テストがクライアントのインスタンスを設定し、次に私が望むように動作するモックオブジェクトを作成できるようにするフックを提供することです。 モックオブジェクトは、再利用を許可するために変更可能である可能性があります。

ユニットのテスト容易性を向上させる特定の互換性のある変更をHttpClientに提案したい場合は、それを提案してください。 @ SidharthNabarと彼のチームがそれを検討できます。

APIがモック可能でない場合は、修正する必要があります。 しかし、それはインターフェースとクラスとは何の関係もありません。 インターフェイスは、すべての実用的な目的で、モックアビリティの観点から純粋な抽象クラスと同じです。

@KrzysztofCwalinaあなた、サー、頭に釘を打ちます! 完璧な要約!

私は個人的にインターフェースが必要だとは思わないので、この議論のあまり人気のない側面を取り上げるつもりだと思います。 すでに指摘したように、 HttpClientはすでに抽象化されています。 動作は実際にはHttpMessageHandlerから発生します。 はい、MoqやFakeItEasyなどのフレームワークで慣れ親しんだアプローチを使用してモック可能/偽造可能ではありませんが、そうである必要はありません。 それは、物事を行うための_唯一の_方法ではありません。 ただし、APIは設計上完全にテスト可能です。

それでは、「自分HttpMessageHandlerを作成する必要がありますか?」について説明しましょう。 いいえ、もちろん違います。 私たち全員が独自のモックライブラリを作成したわけではありません。 @PureKromeは、すでにHttpClient.Helpersライブラリを表示しています。 個人的には使ったことがないのですが、チェックしてみます。 私は@richardszalayMockHttpライブラリを使用してきました。これは素晴らしいと思います。 AngularJSの$ httpBackendを使用したことがある場合、MockHttpはまったく同じアプローチを採用しています。

依存関係に関しては、サービスクラスはHttpClientの注入を許可する必要があり、明らかにnew HttpClient()の妥当なデフォルトを提供できます。 インスタンスを作成できるようにする必要がある場合は、 Func<HttpClient>を使用するか、何らかの理由でFunc<>のファンでない場合は、独自のIHttpClientFactory抽象化とそのデフォルトの実装では、ここでもnew HttpClient()が返されます。

最後に、このAPIは現在の形式で完全にテスト可能であり、インターフェースは必要ないと思います。 はい、別のアプローチが必要ですが、前述のライブラリは、完全に論理的で直感的な方法でこの異なるスタイルのテストを支援するためにすでに存在しています。 より多くの機能/シンプルさが必要な場合は、それらのライブラリを改善するために貢献しましょう。

個人的には、インターフェースや仮想メソッドは私にとってそれほど重要ではありません。 しかし、さあ、 perfectly testableは少しから多めです:)

@luisrudgeさて、MockHttpのようなものが可能にするテストのメッセージハンドラースタイルを使用してテストできないシナリオを与えることができますか? 多分それはケースを作るのを助けるでしょう。

まだコード化できないものはありませんが、カバーできない難解なシナリオがいくつか見当たらない可能性があります。 それでも、誰かが貢献できるそのライブラリの欠落している機能である可能性があります。

今のところ、.NET開発者が慣れている方法ではなく、依存関係として「完全にテスト可能」であるという意見を維持しています。

それはテスト可能です、私は同意します。 しかし、それはまだ苦痛です。 それを行うのに役立つ外部ライブラリを使用する必要がある場合、それは完全にテスト可能ではありません。 しかし、それについて議論するには主観的すぎると思います。

aspnet mvc 5は完全にテスト可能であると主張する人もいるかもしれません。コントローラーが必要とするすべてのものをモックするには、50LOCを記述するだけです。 私見、それはHttpClientの場合と同じです。 それはテスト可能ですか? はい。 テストするのは簡単ですか? いいえ。

@luisrudgeはい、同意します。これは主観的なものであり、インターフェイス/仮想化に対する要望を完全に理解しています。 私は、このスレッドに参加して読んだ人が、 HttpClientの周りに独自の抽象化をすべて導入することなく、このAPIをコードベースで非常にテスト可能な方法で活用できるという事実について少なくともある程度の教育を受けられるようにしようとしています。そこに着くに

それを行うのに役立つ外部ライブラリを使用する必要がある場合、それは完全にテスト可能ではありません

ええと、私たちは皆、すでにモック/フェイクに1つのライブラリを使用しています*そして、確かに、このスタイルのAPIをテストするために_that_を使用することはできませんが、それはテスト可能性が低いことを意味するとは思いません別のライブラリを持ち込む必要があるという理由だけで、インターフェイスベースのアプローチ。

_ *少なくとも私は望んでいません!_

私のOPからの@ drub0yは、ライブラリはテスト可能であると述べましたが、それは(私が情熱的に信じているものと)比較すると、本当に苦痛です。 IMO @luisrudgeはそれを完璧に綴っています:

aspnet mvc 5は完全にテスト可能であると主張する人もいるかもしれません。コントローラーが必要とするすべてのものをモックするには、50LOCを記述するだけです。

このリポジトリは、_多数の_開発者の主要な部分です。 したがって、デフォルトの(そして理解できる)戦術は、このリポジトリに_非常に_注意することです。 確かに-私は完全にそれを理解しています。

このリポジトリは、RTWに移行する前に、(APIに関して)微調整できる状態にあると思います。 将来の変更は突然_本当に_困難になります。変更する_知覚_を含めます。

それで、現在のAPIで-それはテストできますか? うん。 Dark Matter Developerテストに合格していますか? 個人的にはそうは思いません。

リトマス試験IMOはこれです:通常のジョーは、一般的/人気のあるテストフレームワーク+モックフレームワークの1つをピックアップし、このAPIの主要なメソッドのいずれかをモックできますか? 今-いや。 したがって、開発者は自分たちがしていることをやめ、このライブラリの_実装_について学び始める必要があります。

すでに指摘したように、HttpClientはすでに抽象化されています。 動作は実際にはHttpMessageHandlerから来ています。

これが私のポイントです。図書館の目的がすべての魔法を抽象化することであるのに、なぜこれを理解するためにサイクルを費やさなければならないのですか。私たちの魔法...すみません、袖を引き上げたり、図書館の屋根を持ち上げたり、中を掘り始めたりする必要があります。」

それはとても感じます...複雑です。

私たちは、非常に多くの人々の生活を楽にし、「それは可能ですが、FAQ /コードを読んでください」という防御的な立場に戻り続ける立場にあります。

この問題に取り組む別の角度は次のとおりです。ランダムな非MS開発者に2つの例を示します... xUnitとMoq / FakeItEasy / InsertTheHipMockingFrameworkThisYearの使用方法を知っている市民開発者をジョーします。

  • インターフェイス/仮想/何でも使用する最初のAPI ..しかし、メソッドはモックすることができます。
  • 単なるパブリックメソッドであり、_統合ポイント_をモックする2番目のAPI ..コードを読み取る必要があります。

それはそれまで蒸留します、IMO。

どの開発者がその問題を解決できるかを確認し、満足してください。

APIがモック可能でない場合は、修正する必要があります。

今のところそれはIMOではありません-しかしこれをうまく回避する方法があります(繰り返しますが、それは意見です-私はそれを認めます)

しかし、それはインターフェースとクラスとは何の関係もありません。 インターフェイスは、すべての実用的な目的で、モックアビリティの観点から純粋な抽象クラスと同じです。

正確に-それは実装の詳細です。 目標は、戦闘でテストされたモックフレームワークを使用できるようにすることです。

@luisrudgeそれを行うのに役立つ外部ライブラリを使用する必要がある場合、完全にテスト可能ではありません
@ drub0yええと、私たちは皆、すでにモック/偽造のために何らかのライブラリを使用しています

(最後の引用/段落を理解したと思います。)静かではありません。 @luisrudgeが言っていたのは、「テスト用のツールが1つあります。モック用の2つ目のツールです。これまでのところ、これらの汎用/全体的なツールは何にも関連付けられていません。しかし、.. _specific_である3​​つ目のツールをダウンロードする必要があります。私たちが使用している特定のAPI /サービスは、うまくテストされるように設計されていなかったため、コードで_specific_ API /サービスをモックすることにしましたか?」 それは少しリッチです:(

依存関係に関しては、サービスクラスでHttpClientを挿入できるようにする必要があり、明らかに新しいHttpClient()の妥当なデフォルトを提供できます。

完全に同意しました! では、上記のサンプルコードをリファクタリングして、これがどれほど簡単であるかを示していただけますか? 猫の皮を剥ぐ方法はたくさんあります...では、シンプルで簡単な方法は何ですか? コードを見せてください。

始めます。 インターフェースを使用してくれた@KrzysztofCwalinaに謝罪しますが、これは私のリトマス試験を開始するためだけのものです。

public interface IHttpClient
{
    Task<string> GetStringAsync(string url);
}

public class SomeService
{
    private IHttpClient _httpClient;

    // Injected.
    public SomeService(IHttpClient httpClient) { .. }

    public async Task<string> GetSomeData(string url)
    {
        string result;
        using (var httpClient = _httpClient ?? new HttpClient())
        {
            result = await httpClient.GetStringAsync(url);
        }

        return result;
    }    
}

@PureKromeに必要なのは、テスト中のAPIの動作をカスタマイズするためにモック/オーバーライドするメソッドを説明するドキュメントの更新だけのようです。

@sharwellそれは絶対に当てはまりません。 テストするときは、httpclientコード全体を実行したくありません。
SendAsyncメソッドを見てください。
それは非常識です。 あなたが提案しているのは、すべての開発者がクラスの内部を知り、githubを開き、コードとそのようなものを見てテストできるようにする必要があるということです。 私はその開発者にGetStringAsyncをあざけって、たわごとを成し遂げてもらいたい。

@luisrudge実際、私の提案は、このライブラリをあざけるのを完全に避けることです。 分離された交換可能な実装を備えたクリーンな抽象化レイヤーがすでにあるため、追加のモックは不要です。 このライブラリのモックには、次の追加の欠点があります。

  1. テストを作成する(モックを維持する)開発者の負担が増えるため、開発コストが増加します
  2. メッセージに関連付けられたコンテンツストリームの再利用など、テストで特定の問題のある動作を検出できない可能性が高くなります(メッセージを送信すると、コンテンツストリームのDisposeが呼び出されますが、ほとんどのモックはこれを無視します)
  3. アプリケーションと特定の抽象化レイヤーとの不必要に緊密な結合。これにより、 HttpClientの代替案の評価に関連する開発コストが増加します。

モックは、小規模でテストするのが難しい絡み合ったコードベースを対象としたテスト戦略です。 モックの使用は開発コストの増加と相関しますが、モックの_必要性_はコード内の結合量の増加と相関します。 これは、モック自体がコードの臭いであることを意味します。 モックを使用せずにAPI全体およびAPI内で同じ入出力テストカバレッジを提供できれば、基本的にすべての開発面でメリットが得られます。

分離された交換可能な実装を備えたクリーンな抽象化レイヤーがすでにあるため、追加のモックは不要です

ちなみに、オプションが独自の偽のメッセージハンドラーを作成する必要がある場合、これはコードを掘り下げないと明らかではありませんが、それは非常に高い障壁であり、多くの開発者にとって非常に苛立たしいものになります。

以前の@luisrudgeのコメントに同意します。 _技術的に_MVC5はテスト可能でしたが、私の神はそれがお尻の痛みと髪を引っ張る巨大な源でした。

@sharwell IHttpClientをあざけるのは負担であり、偽のメッセージハンドラーを実装すると言っていますか(これはそうではありませんか?申し訳ありませんが、同意できません。

@luisrudge GetStringAsyncをテストする単純なケースの場合、ハンドラーをモックするのはそれほど難しくありません。 すぐに使用できるMoq + HttpClientを使用すると、必要なのは次のとおりです。

Uri requestUri = new Uri("http://google.com");
string expectedResponse = "Response text";

Mock<HttpClientHandler> mockHandler = new Mock<HttpClientHandler>();
mockHandler.Protected()
    .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(message => message.RequestUri == requestUri), ItExpr.IsAny<CancellationToken>())
    .Returns(Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(expectedResponse) }));
HttpClient httpClient = new HttpClient(mockHandler.Object);
string result = await httpClient.GetStringAsync(requestUri).ConfigureAwait(false);
Assert.AreEqual(expectedResponse, result);

それが多すぎる場合は、ヘルパークラスを定義できます。

internal static class MockHttpClientHandlerExtensions
{
    public static void SetupGetStringAsync(this Mock<HttpClientHandler> mockHandler, Uri requestUri, string response)
    {
        mockHandler.Protected()
            .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(message => message.RequestUri == requestUri), ItExpr.IsAny<CancellationToken>())
            .Returns(Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(response) }));
    }
}

ヘルパークラスを使用するために必要なのはこれだけです。

Uri requestUri = new Uri("http://google.com");
string expectedResponse = "Response text";

Mock<HttpClientHandler> mockHandler = new Mock<HttpClientHandler>();
mockHandler.SetupGetStringAsync(requestUri, expectedResponse);
HttpClient httpClient = new HttpClient(mockHandler.Object);
string result = await httpClient.GetStringAsync(requestUri).ConfigureAwait(false);
Assert.AreEqual(expectedResponse, result);

したがって、2つのバージョンを比較するには:
_免責事項_:これはテストされていないブラウザコードです。 また、私は昔からMoqを使用していません。

電流

public class SomeService(HttpClientHandler httpClientHandler= null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _handler == null
                                  ? new HttpClient
                                  : new HttpClient(handler))
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

// Unit test...
// Arrange.
Uri requestUri = new Uri("http://google.com");
string expectedResponse = "Response text";
var responseMessage = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(expectedResponse) };
Mock<HttpClientHandler> mockHandler = new Mock<HttpClientHandler>();
mockHandler.Protected()
    .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(message => message.RequestUri == requestUri), ItExpr.IsAny<CancellationToken>())
    .ReturnsAsync(responseMessage);
HttpClient httpClient = new HttpClient(mockHandler.Object);
var someService = new SomeService(mockHandler.Object); // Injected...

// Act.
string result = await someService.GetSomethingFromTheInternetAsync();

// Assert.
Assert.AreEqual(expectedResponse, result);
httpClient.Verify(x => x.GetSomethingFromTheInternetAsync(), Times.Once);

APIメソッドを直接モックする方法を提供するのとは対照的です。

別の方法

public class SomeService(IHttpClient httpClient = null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _httpClient ?? new HttpClient())
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

// Unit tests...
// Arrange.
var response = new Foo();
var httpClient = new Mock<IHttpClient>();
httpClient.Setup(x => x.GetStringAsync(It.IsAny<string>))
    .ReturnsAsync(response);
var service = new SomeService(httpClient.Object); // Injected.

// Act.
var result = await service.GetSomethingFromTheInternetAsync();

// Assert.
Assert.Equals("something", foo.Something);
httpClient.Verify(x => x.GetStringAsync(It.IsAny<string>()), Times.Once);

それを蒸留すると、それは主にこれになります( SomeServiceのわずかな違いを除く)...

var responseMessage = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(expectedResponse) };
Mock<HttpClientHandler> mockHandler = new Mock<HttpClientHandler>();
mockHandler.Protected()
    .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(message => message.RequestUri == requestUri), ItExpr.IsAny<CancellationToken>())
    .ReturnsAsync(responseMessage);
HttpClient httpClient = new HttpClient(mockHandler.Object);

vsこれ..。

var httpClient = new Mock<IHttpClient>();
httpClient.Setup(x => x.GetStringAsync(It.IsAny<string>))
    .ReturnsAsync(response);

コードは_大まかに_両方に適していますか? (2つを比較する前に、両方ともかなり正確である必要があります)。

@sharwellなので、テストを実行するには、すべてのHttpClient.csコードでコードを実行する必要がありますか? それはどのようにHttpClientとの結合が少ないのですか?

一部のシステムではHttpClientを多用しており、完全にテスト可能な技術とは言えません。 私はライブラリが本当に好きですが、テストのために何らかのインターフェイスやその他の改善を行うことは、IMOの大きな改善になるでしょう。

工藤は彼のライブラリのために@PureKromeに行きますが、それが必要でなければもっと良いでしょう:)

すべてのコメントを読んで多くのことを学びましたが、質問が1つあります。それは、テストでHttpClientをモックする必要があるのはなぜですか。

最も一般的なシナリオでは、 HttpClientによって提供される機能は、$ HtmlDownloader.DownloadFile()などのクラスにすでにラップされています。 配管の代わりにこのクラスをモックしないのはなぜですか?

このようなモックが必要になる可能性のあるもう1つのシナリオは、ダウンロードした文字列を解析する必要があり、このコードで複数の異なる出力をテストする必要がある場合です。 この場合でも、この機能(解析)をクラス/メソッドに抽出して、モックなしでテストすることができます。

私はそれのテスト容易性が改善できないと言っているのではなく、それが改善されるべきではないという意味でもありません。 私はただいくつかの新しいことを学ぶために理解したいです。

@tucaz自分に関する情報を返すWebサイトがあると想像してみてください。 あなたの年齢、名前、誕生日など。あなたはまた、いくつかの会社に株を持っています。

あなたは100ユニットのAAA株と200ユニットのBBB株を所有していると言いましたか? したがって、アカウントの詳細を表示するときは、株式の価値を表示する必要があります。

これを行うには、_another_システムから現在の株価を取得する必要があります。 これを行うには、サービスを使用して株式を取得する必要がありますよね? これにしましょう...

_免責事項:ブラウザコード..._

public class StockService : IStockService
{
    private readonly IHttpClient _httpClient;

    public StockService(IHttpClient httpClient)
    {
        _httpClient = httpClient; // Optional.
    }

    public async Task<IEnumerable<StockResults>> GetStocksAsync(IEnumerable<string> stockCodes)
    {
        var queryString = ConvertStockCodeListIntoQuerystring();
        string jsonResult;
        using (var httpClient = _httpClient ?? new HttpClient())
        {
            jsonResult = await httpClient.GetStringAsync("http:\\www.stocksOnline.com\stockResults?" + queryString);
        }

        return JsonConvert.DeserializeObject<StockResult>(jsonResult);
    }
}

さて、ここに次のような簡単なサービスがあります。「_ AAA株とBBB株の株価を取得してください_」;

だから、私はこれをテストする必要があります:

  • いくつかの合法的な株名を与えられた
  • いくつかの無効な/存在しない株式名が与えられた
  • httpClientが例外をスローします(応答を待っている間にインターネットが爆発しました)

そして今、私はそれらのシナリオを簡単にテストすることができます。 httpClientが爆発したとき(例外をスローしたとき)に気づきました..このサービスが爆発した..それで、ロギングを追加してnullを返す必要があるかもしれませんか? donno ..しかし、少なくとも私は問題について考えて、それを処理することができます。

もちろん、私は通常の単体テスト中に実際のWebサービスに実際にアクセスしたくありません。 モックを注入することで、それを使用できます。 それ以外の場合は、新しいインスタンスを作成して使用できます。

だから私は私の提案が現在の現状よりもはるかに簡単な(読む/維持する/など)ヘラバであることを確立しようとしています。

最も一般的なシナリオでは、HttpClientによって提供される機能は、HtmlDownloader.DownloadFile()などのクラスにすでにラップされています。

なぜHttpClientをラップする必要があるのですか? これは、下にあるすべてのhttp:sparkles:を_すでに_抽象化したものです。 それは素晴らしい図書館です! 私は個人的にそれが何を達成するのか分かりませんか?

@PureKromeの使用例は、機能をラッピングクラスでラッピングするという意味です。

あなたが何をテストしようとしているのか理解できるかどうかはわかりませんが、私にとって、あなたが本当に主張する必要があるのは、それを消費している人は誰でもGetStockAsyncから戻ってくるものを処理できるということであるときに、基盤となるWebサービスをテストしているようです

次の点を考慮してください。

private IStockService stockService;

[Setup]
public void Setup()
{
    stockService = A.Fake<IStockService>();

    A.CallTo(() => stockService.GetStocksAsync(new [] { "Ticker1" }))
        .Returns(new StockResults[] { new StockResults { Price = 100m, Ticker = "Ticker1", Status = "OK" }});
    A.CallTo(() => stockService.GetStocksAsync(new [] { "Ticker2" }))
        .Returns(new StockResults[] { new StockResults { Price = 0m, Ticker = "Ticker2", Status = "NotFound" }});
    A.CallTo(() => stockService.GetStocksAsync(new [] { "Ticker3" }))
        .Throws(() => new InvalidOperationException("Some weird message"));
}

[Test]
public async void Get_Total_Stock_Quotes()
{
    var stockService = A.Fake<IStockService>();

    var total = await stockService.GetStockAsync(new [] { "Ticker1" });

    Assert.IsNotNull(total);
    Assert.IsGreaterThan(0, total.Sum(x => x.Price);
}

[Test]
public async void Hints_When_Ticker_Not_Found()
{
    var stockService = A.Fake<IStockService>();

    var total = await stockService.GetStockAsync(new [] { "Ticker2" });

    Assert.IsNotNull(total);
    Assert.AnyIs(x => x.Status == "NotFound");
}

[Test]
public async void Throws_InvalidOperationException_When_Error()
{
    var stockService = A.Fake<IStockService>();

    Assert.Throws(() => await stockService.GetStockAsync(new [] { "Ticker3" }), typeof(InvalidOperationException));
}

GetStockAsyncメソッドの動作に関してテストする必要のあるものは何もないので、抽象化を1レベル上げて、実際のビジネスロジックに対処することを提案します。

HttpClientをモックする必要があると私が考えることができる唯一のシナリオは、サーバーから受信した応答などに応じて、再試行ロジックをテストする必要がある場合です。 この場合、おそらくHttpClientWithRetryLogic HttpClientクラスを使用するすべてのメソッドに拡散されるわけではない、より複雑なコードを作成することになります。

この場合、提案されたメッセージハンドラーを使用するか、クラスを作成してこの再試行ロジックをすべてラップできます。これは、 HttpClientがクラスが行う重要なことの非常に小さな部分になるためです。

@tucaz偽物が設定したものを返すことをテストしただけでなく、モックライブラリを効果的にテストしましたか? @PureKromeは、具体的なStockService実装をテストしたいと考えています。

新しく導入された抽象化レベルでは、 StockServiceを適切にテストできるように、 HttpClient呼び出しをラップする必要があります。 ラッパーがHttpClientへのすべての呼び出しを単に委任し、アプリケーションに不必要な複雑さを追加する可能性があります。

いつでも新しい抽象化レイヤーを追加して、モックできないタイプを非表示にすることができます。 このスレッドで批判されているのは、 HttpClientをあざけることは可能ですが、おそらく他のライブラリで通常行われていることと同じように、もっと簡単なはずです。

@MrJulあなたは正しいです。 そのため、彼の例でテストされたのはWebサービスの実装そのものであり、(コード例なしで。私の悪い点ですが)彼がIStockServiceをモックして、それを消費するコードがGetStockAsyncが返すものは何でも処理します。

それは私が最初にテストされるべきだと思うものです。 モックフレームワーク(私がやったように)やWebサービスの実装( @PureKromeがやりたいように)ではありません。

結局のところ、 IHttpClientインターフェイスがあればいいのですが、IMOをテストする必要があるのはサービス抽象化の利用者であるため、この種のシナリオではユーティリティが見当たらないことは理解できます。 (したがって、Webサービス)は、その戻りを適切に処理できます。

@tucazいいえ。 あなたのテストは間違っています。 モックライブラリをテストしています。 @PureKromeの例を使用すると、次の3つのことをテストする必要があります。

  • httpリクエストが正しいMETHODを使用している場合
  • httpリクエストに適切なURLがある場合
  • 応答本文をjsonデシリアライザーで正常に逆シリアル化できるかどうか

たとえば、コントローラーでIStockServiceをモックするだけの場合は、上記の3つのことのいずれもテストしていません。

@luisrudgeはあなたのすべての点に同意します。 しかし...あなたが列挙するこれらすべてのものをテストすることの価値はそれだけの価値がありますか? つまり、複数のフレームワーク(json.net、httpclientなど)でカバーされているすべての基本的なことをテストすることに専念しているのであれば、今日可能な方法で処理できると思います。

それでも、奇妙な応答を逆シリアル化できることを確認したい場合は、テスト内にHttpClientを追加せずに、コードのこれらの側面を分離するテストを行うことはできませんか? また、サーバーがテストで期待している正しい応答を常に送信することを防ぐ方法はありません。 カバーされていない応答がある場合はどうなりますか? あなたのテストはそれを取得せず、とにかく問題が発生します。

私の意見では、あなたが提案しているそのようなテストの正味価値は最小限であり、アプリケーションの他の99%がカバーされている場合にのみそれに取り組む必要があります。 しかし、結局のところ、それはすべて個人的な好みの問題であり、これはさまざまな人々がさまざまな価値を見ている主観的な主題です。 もちろん、これはHttpClientがよりテスト可能であってはならないという意味ではありません。

この特定のコードの設計は、これらすべての点を考慮に入れていると思います。最適な結果でなくても、今すぐ変更するコストが、考えられるメリットよりも低くなるとは思いません。

私が最初に話したときに私が念頭に置いていた目的は、 @ PureKromeが私が認識していることに集中しないようにすることでした。 しかし、繰り返しになりますが、それはすべて、ある時点で個人的な意見に帰着します。

正しいURLをヒットしたことを確認すると、正味の価値が低くなりますか? 投稿の代わりにリソースを編集するためにPUTリクエストを実行することを確認するのはどうですか? 私のアプリの他の99%はカバーできますが、この呼び出しが失敗した場合、何も機能しません。

すっごく、 HttpClient::GetStringAsyncについて話すことは、そもそもHttpClientをモックできることを主張するのに悪い方法だと思います。 スローされるHttpRequestExceptionの背後にすべてが隠されているため、適切なエラー処理を実行できないために、すでに失ったHttpClient::Get[String|Stream|ByteArray]Asyncメソッドのいずれかを使用している場合は、 HTTPステータスコードなどの重要な詳細も公開しません。 つまり、 HttpClientの上に構築した抽象化には、特定のHTTPエラーに反応してドメイン固有の例外に変換する機能がなく、リークのある抽象化が発生します...これにより、ドメインサービスの呼び出し元にひどい例外情報が提供されます。

これを指摘することは重要だと思います。ほとんどの場合、これは、モックアプローチを使用している場合、 HttpClientメソッドをモックして$ HttpResponseMessageを返すことを意味します。 HttpMessageHandler::SendAsyncをモック/偽造するために必要な作業量と同じです。 実際、それだけではありません。 $#$ HttpMessageHandler $#$レベルで単にSendAsyncをモックするのではなく、 HttpClientレベルで複数のメソッド(たとえば[Get|Put|Delete|Send]Async )をモックする必要がある可能性があります。

@luisrudgeはJSONの逆シリアル化についても言及しているので、今はHttpContentでの作業について話しています。これは、確実にHttpResponseMessageを返すメソッドの1つで作業していることを意味します。 メディアフォーマッタアーキテクチャ全体をバイパスし、 GetStringAsyncからオブジェクトインスタンスに文字列をJsonSerializer::Deserializeなどを使用して手動で逆シリアル化すると言っている場合を除きます。 その場合、APIを完全に間違って使用しているだけなので、APIのテストについて実際に話し合うことはできないと思います。 :残念だった:

@luisrudgeそれは確かに重要ですが、それが正しいことを確認するためにHttpClientに触れる必要はありません。 URLを読み取ったソースが正しく戻ってきて、問題がないことを確認してください。 繰り返しますが、 HttpClientを含む統合テストを行う必要はありません。

これらは重要なことですが、一度正しく理解すれば、どこかに漏れているパイプがない限り、それを台無しにする可能性は本当に低くなります。

@tucazは同意します。 それでも:それは最も重要な部分であり、私はそれをテストしてもらいたいです:)

@ drub0yパブリックメソッドの使用が間違っていることに驚いています:)

@luisrudgeええと、私が思うに別の議論ですが、コーディングシナリオの最も改善されたものを除いて、これらの「便利な」メソッドの使用に固有の欠陥を無視することによってモックできるインターフェースのケースを作っているとは思いません。 (例:ユーティリティアプリやステージデモ)。 それらを本番コードで使用することを選択した場合、それはあなたの選択ですが、うまくいけばそれらの欠点を認めながら使用します。

とにかく、 @ PureKromeが見たかったことに戻りましょう。これは、コードを簡単にテストできるように、 HttpClient API設計に適合するようにクラスを設計する方法です。

まず、ドメインのニーズを満たすためにHTTP呼び出しを行うドメインサービスの例を次に示します。

`` `c#
パブリックインターフェイスISomeDomainService
{{
タスクGetSomeThingsValueAsync(string id);
}

パブリッククラスSomeDomainService:ISomeDomainService
{{
プライベート読み取り専用HttpClient_httpClient;

public SomeDomainService() : this(new HttpClient())
{

}

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

public async Task<string> GetSomeThingsValueAsync(string id)
{
    return await _httpClient.GetStringAsync("/things/" + Uri.EscapeUriString(id));
}

}

As you can see, it allows an `HttpClient` instance to be injected and also has a default constructor that instantiates the default `HttpClient` with no other special settings (obviously this default instance can be customized, but I'll leave that out for brevity).

Ok, so what's a test method look like for this thing using the MockHttp library then? Let's see:

``` c#
[Fact]
public async Task GettingSomeThingsValueReturnsExpectedValue()
{
    // Arrange
    var mockHttpMessageHandler = new MockHttpMessageHandler();
    mockHttpMessageHandler.Expect("http://unittest/things/123")
        .Respond(new StringContent("expected value"));

    SomeDomainService sut = new SomeDomainService(new HttpClient(mockHttpMessageHandler)
    {
        BaseAddress = new Uri("http://unittest")
    });

    // Act
    var value = await sut.GetSomeThingsValueAsync("123");

    // Assert
    value.Should().Be("expected value");
}

えーと、それはとても単純で簡単です。 日IMNSHOとして明確に読み取ります。 また、私はFluentAssertionsを利用していることに注意してください。これもまた、私たちの多くが使用していると思う別のライブラリであり、「別のライブラリを持ち込む必要はない」という議論に対する別のストライキです。

ここで、プロダクションコードにGetStringAsyncを使用したくない理由についても説明します。 SomeDomainService::GetSomeThingsValueAsyncメソッドを振り返り、実際に意味のあるHTTPステータスコードを返すRESTAPIを呼び出していると想定します。 たとえば、IDが「123」の「thing」が存在しないため、APIが404ステータスを返す可能性があります。 だから私はそれを検出し、それを次のドメイン固有の例外としてモデル化したいと思います:

`` `c#
//簡潔にするためにシリアル化のサポートは除外されています
パブリッククラスSomeThingDoesntExistException:例外
{{
public SomeThingDoesntExistException(string id)
{{
Id = id;
}

public string Id
{
    get;
    private set;
}

}

Ok, so let's rewrite the `SomeDomainService::GetSomeThingsValueAsync` method to do that now:

``` c#
public async Task<string> GetSomeThingsValueAsync(string id)
{
    try
    {
        return await _httpClient.GetStringAsync("/things/" + Uri.EscapeUriString(id));
    }
    catch(HttpRequestException requestException)
    {
        // uh, oh... no way to tell if it doesn't exist (404) or server maybe just errored (500)
    }
}

つまり、 GetStringAsyncのような基本的な「便利な」メソッドは、実稼働レベルのコードには実際には「適切」ではないと言ったときに話していたのです。 取得できるのはHttpRequestExceptionだけであり、そこから取得したステータスコードがわからないため、ドメイン内の正しい例外に変換できない可能性があります。 代わりに、 GetAsyncを使用して、 HttpResponseMessageを次のように解釈する必要があります。

`` `c#
publicasyncタスクGetSomeThingsValueAsync(string id)
{{
HttpResponseMessage responseMessage = await _httpClient.GetAsync( "/ things /" + Uri.EscapeUriString(id));

if(responseMessage.IsSuccessStatusCode)
{
    return await responseMessage.Content.ReadAsStringAsync();
}
else
{
    switch(responseMessage.StatusCode)
    {
        case HttpStatusCode.NotFound:
            throw new SomeThingDoesntExistException(id);

        // any other cases you want to might want to handle specifically for your domain

        default:
            // Unhandled cases can throw domain specific lower level communication exceptions
            throw new HttpCommunicationException(responseMessage.StatusCode, responseMessage.ReasonPhrase);
    }
}

}

Ok, so now let's write a test to validate that I get my domain specific exception when I request a URL that doesn't exist:

``` C#
[Fact]
public void GettingSomeThingsValueForIdThatDoesntExistThrowsExpectedException()
{
    // Arrange
    var mockHttpMessageHandler = new MockHttpMessageHandler();

    SomeDomainService sut = new SomeDomainService(new HttpClient(mockHttpMessageHandler)
    {
        BaseAddress = new Uri("http://unittest")
    });

    // Act
    Func<Task> action = async () => await sut.GetSomeThingsValueAsync("SomeIdThatDoesntExist");

    // Assert
    action.ShouldThrow<SomeThingDoesntExistException>();
}

Ermahgerdそれはさらに少ないコードです!! $!%なぜですか? なぜなら、MockHttpで期待値を設定する必要さえなく、デフォルトの動作として、要求されたURLに対して404が自動的に返されるからです。

Magic is Real

@PureKromeは、サービスクラス内から新しいHttpClientインスタンスをインスタンス化する場合についても言及しました。 前に言ったように、その場合は、実際の作成から自分自身を抽象化する代わりに、依存関係としてFunc<HttpClient>を取るか、 Funcのファンでない場合は、独自のファクトリインターフェイスを導入できます。なんらかの理由で

返信を削除しました- @ drub0yの返信について考えて少しコーディングしたいと思います。

@ drub0y誰かが、そもそもMockHttpMessageHandlerを実装する必要があることを理解する必要があることを忘れました。 それがこの議論の要点です。 それは十分に単純ではありません。 aspnet5を使用すると、仮想メソッドを使用してインターフェイスまたは基本クラスからパブリッククラスをモックするだけですべてをテストできますが、この特定のAPIエントリは異なり、検出しにくく、直感的ではありません。

@luisrudge確かに、私はそれに同意できますが、MoqまたはFakeItEasyが必要であるとどうやって理解したのでしょうか。 N / XUnit、FluentAssertionsなどが必要であることをどのようにして理解しましたか? 私はこれを実際には何の違いも見ていません。

コミュニティには、このようなものの必要性を認識し、実際に時間をかけてこれらの問題を解決するためにこのようなライブラリを起動する素晴らしい(賢い)人々がいます。 (彼ら全員に称賛を!)そして、実際に「良い」解決策は、同じニーズを認識し、OSSとコミュニティの他のリーダーによる伝道を通じて有機的に成長し始める他の人々に注目されます。 OSSの力が.NETコミュニティでようやく実現されたことに関して、これまで以上に状況は良くなっていると思います。

そうは言っても、私は個人的に、Microsoftが「箱から出して」すぐにSystem.Net.Http APIを提供するとともに、MockHttpのようなものを提供すべきだったと信じています。 AngularJSチームは$httpBackEnd$httpを提供しました。これは、人々がアプリを効果的にテストできるようにするために、この機能が絶対に必要であると認識したためです。 Microsoftがこれと同じ問題を特定しなかったという事実が、このAPIのベストプラクティスに多くの「混乱」がある理由だと思います。そして、私たち全員が今この問題について話し合っている理由だと思います。 :失望:そうは言っても、@ richardszalayと@PureKromeが彼らのライブラリとのギャップを埋めるためにプレートにステップアップしてくれてうれしいです、そして今私たちは彼らのライブラリを改善するのを助けるために彼らの後ろに立つべきだと思います私たちの生活を楽にします。 :笑顔:

このスレッドの通知を受け取ったばかりなので、2cを投入すると思いました。

私は次の人と同じくらい動的なモックフレームワークを使用していますが、近年、一部のドメイン(HTTPなど)は、より多くのドメイン知識を持つ何かの背後に偽造された方がよいことを理解するようになりました。 基本的なURLマッチング以外のものは、動的モックを使用するとかなり苦痛になります。それを繰り返すと、とにかくMockHttpが行うことの独自のミニバージョンになります。

別の例としてRxを取り上げます。IObservableはインターフェイスであるためモック可能ですが、動的モックだけで複雑な構成(スケジューラーは言うまでもなく)をテストするのは狂気です。 Rxテストライブラリは、それが機能するドメインにはるかに意味的な意味を与えます。

特にHTTPに戻ると、JSの世界では、AngularのテストAPIを使用するのではなく、sinonを使用してXMLHttpRequestをモックすることもできますが、誰かが使用した場合はかなり驚きます。

そうは言っても、私は発見可能性について完全に同意します。 AngularのHTTPテストドキュメントはそのHTTPドキュメントとともにそこにありますが、それを探すにはMockHttpについて知る必要があります。 そのため、CoreFxチームのいずれかが連絡を取りたい場合は、MockHttpをメインライブラリに戻すことで完全に問題ありません(ライセンスはすでに同じです)。

私はそれをあざけるのが好きですが、 Func署名も使用します。

public static async Task<T> GetWebObjectAsync<T>(Func<string, Task<string>> getHtmlAsync, string url)
{
    var html = await getHtmlAsync(url);
    var info = JsonConvert.DeserializeObject<T>(html);
    return info;
}

これにより、Testクラスコードを次のように単純にすることができます。

Func<string, Task<string>> getHtmlAsync = u => Task.FromResult(EXPECTED_HTML);
var result = controller.InternalCallTheWeb(getHtmlAsync);

そして、コントローラーは簡単に次のことができます。

public static HttpClient InitHttpClient()
{
    return _httpClient ?? (_httpClient = new HttpClient(new WebRequestHandler
    {
        CachePolicy = new HttpRequestCachePolicy(HttpCacheAgeControl.MaxAge, new TimeSpan(0, 1, 0)),
        Credentials = new NetworkCredential(PublishingCredentialsUserName, PublishingCredentialsPassword)
    }, true));
}

[Route("Information")]
public async Task<IHttpActionResult> GetInformation()
{
    var httpClient = InitHttpClient();
    var result = await InternalCallTheWeb(async u => await httpClient.GetStringAsync(u));
    ...
}

しかし、彼らはそれを少し簡単にすることができます... :)

こんにちは.NETチーム! この問題についてベースに触れるだけです。 1か月以上経ちましたが、チームがこのディスカッションについてどう思っているかについての考えに興味があります。

キャンプの両側からさまざまなポイントがありました-すべて興味深く関連性のあるIMO。

あなたのギャル/みんなからの更新はありますか?

すなわち。

  1. 私たちはコンボを読み、物事を変えることはありません。 ご意見ありがとうございます。素敵な一日を。 ここに、:cakeの一部があります:
  2. 私たちは正直にそれについて考えていません(あなたが想像できるように、この辺りはとても忙しいです)_しかし_私たちがRelease-To-Web(RTW)に行く前に_後日それについて考えるでしょう。
  3. 実際、私たちはそれについていくつかの内部的な議論をしました、そして私たちは一般の人々がそれを少し苦痛に感じていることを認めます。 だから私たちはXXXXXXXXXをするかもしれないと思っています。

た!

パート2と3です。

テストに役立つ「モック」ハンドラーを挿入する機能を簡素化する1つの方法は、静的メソッドをHttpClient、つまりHttpClient.DefaultHandlerに追加することです。 この静的メソッドを使用すると、開発者は、デフォルトのHttpClient()コンストラクターを呼び出すときに、現在のHttpClientHandlerとは異なるデフォルトのハンドラーを設定できます。 「モック」ネットワークに対するテストを支援することに加えて、HttpClientインスタンスを直接作成しないHttpClientより上のポータブルライブラリにコードを書き込む開発者を支援します。 したがって、この代替のデフォルトハンドラーを「注入」することができます。

したがって、これは、現在のアーキテクチャに重大な変更を加えるのではなく、追加的なものとして現在のオブジェクトモデルに追加できるものの一例です。

非常に迅速な返信をありがとうヒープ@davidsh :+1:

このスペースがパート1ではないことを知ってワクワクするので、私は喜んで待ってこのスペースを見ていきます:smile:..そしてどんな変化が起こるかを見るのを楽しみにしています:+1:

忍耐と耳を傾けてくれてありがとう(チーム)-本当に本当に感謝しています!

/ meは喜んで待っています。

@davidshだから、仮想メソッドのインターフェースは問題外ですか?

それは問題のoufではありません。 ただし、この問題のスレッドで提案されているように、インターフェイスを短期的に追加するには、慎重な調査と設計が必要です。 HttpClientの既存のオブジェクトモデルに簡単にマッピングすることはできず、APIサーフェスに重大な変更を加える可能性があります。 したがって、設計/実装が迅速または簡単なものではありません。

仮想メソッドはどうですか?

はい、仮想メソッドの追加は重大な変更ではなく、追加的な変更です。 この分野でどのような設計変更が可能かについては、まだ検討中です。

/ meはこのスレッドにResurrectionをキャストします...

〜糸のねじれ、うめき声​​、ひねり、そして生き返ります!


2015年8月20日のコミュニティスタンドアップを聞いていたところ、HttpClientはBeta7とのクロスプラットで利用できるとのことでした。 わーい!

それで...これについて何か決定はありましたか? どちらも提案していません/または..ディスカッションの更新などを丁寧に求めていますか?

@PureKromeこれについての決定はありませんが、このAPIの設計/調査作業は将来の計画に組み込まれています。 現在、私たちの大多数は、残りのSystem.NetコードをGitHubに取り込み、クロスプラットフォームで動作することに非常に集中しています。 これには、既存のソースコードとテストのXunitフレームワークへの移植の両方が含まれます。 この取り組みが収束すると、チームはこのスレッドで提案されているような将来の変更に焦点を合わせ始める時間が増えます。

ありがとうヒープ@ davidsh-返信する時間を本当にありがとう:)素晴らしい仕事を続けてください! :バルーン:

httpサービスのテストにモックを使用するのは少し時代遅れではありませんか? httpサービスの簡単なモックを可能にし、コードの変更を必要とせず、サービスのURLを変更するだけの、かなりの数のセルフホスティングオプションがあります。 つまり、HttpListenerとOWINセルフホストですが、複雑なAPIの場合は、モックサービスを構築したり、オートレスポンダーを使用したりすることもできます。

image

image

このHttpClientクラスのユニットテスト機能に関する更新はありますか? DeleteAsyncメソッドをモックしようとしていますが、前述のように、Moqは関数が仮想ではないことについて不平を言っています。 独自のラッパーを作成する以外に簡単な方法はないようです。

私はRichardszalay.Mockhttpに投票します!
素晴らしいです! シンプルで素晴らしい!

https://github.com/richardszalay/mockhttp

しかし、 @ YehudahA-私たちはそれを必要とすべきではありません....(それがこのコンボのポイントです):)

@PureKrome IHttpClientは必要ありません。
これはEF7と同じ考え方です。IDbContextまたはIDbSetを使用することはありませんが、DbContextOptionsを使用して動作を変更するだけです。

Richardszalay.MockHttpは、すぐに使用できるソリューションです。

これは、すぐに使用できるソリューションです。

IHttpClientは必要ありません。
これはEF7と同じ考え方です。IDbContextまたはIDbSetを使用することはありませんが、DbContextOptionsを使用して動作を変更するだけです。

それは大丈夫です-私は完全に同意しないので、私たちは同意しないことに同意します。

両方の方法が機能しますか? はい。 一部の人々(私のような)は、発見可能性、読みやすさ、単純さなどの理由で、インターフェースや仮想メソッドをより簡単にモックすることに気づきます。

他の人(あなた自身のような)は行動を操作するのが好きです。 より複雑で、発見が難しく、時間がかかることがわかりました。 (これは(私にとって)サポート/保守が難しいことを意味します)。

それぞれ独自に、私は推測します:)

Richardszalay氏は次のように述べています。「このパターンはAngularJSの$ httpBackendに大きく影響を受けています」。
それは正しい。 ここを見てみましょう: https ://docs.angularjs.org/api/ngMock/service/ $ httpBackend

@PureKrome
はい、それは簡単です。
使用されているクラスとメソッドを知る必要はありません。モックライブラリ/インターフェイス/仮想メソッドも必要ありません。HttpClientから別のものに切り替えても、セットアップは変更されません。

        private static IDisposable FakeService(string uri, string response)
        {
            var httpListener = new HttpListener();
            httpListener.Prefixes.Add(uri);

            httpListener.Start();

            httpListener.GetContextAsync().ContinueWith(task =>
            {
                var context = task.Result;

                var buffer = Encoding.UTF8.GetBytes(response);

                context.Response.OutputStream.Write(buffer, 0, buffer.Length);

                context.Response.OutputStream.Close();
            });

            return httpListener;
        }

使用法:

            var uri = "http://localhost:8888/myservice/";
            var fakeResponse = "Hello World";

            using (FakeService(uri, fakeResponse))
            {
                // Run your test here ...

                // This is only to test that is really working:
                using (var client = new HttpClient())
                {
                    var result = client.GetStringAsync(uri).Result;
                }
            }

@metzgithubは、ターゲットサービスの動作を適切にシミュレーションできるため、ここでは最適です。

これは、「不正な形式/悪意のある」データを配置するのに最適な場所を提供するため、まさに私が探していたものです(セキュリティおよび非ハッピーパステスト用)

私はこのスレッドに遅れています。人々がHttpClientへの依存関係を単体テストする方法を検索した後、ここに到着しました。これを行うための非常に独創的な方法をいくつか見つけました。 私はこの議論の中で、私はインターフェースを持っていることの側にしっかりと降りてくるだろうと前もって述べます。 しかし、私にとっての摩擦は、私には選択の余地がないということです。

私は、他のコンポーネントへの依存を宣言するコンポーネントとサービスを設計および開発することを選択します。 コンストラクターを介してこれらの依存関係を宣言し、実行時にIoCコンテナーに依存して、この時点でシングルトンまたは一時的なインスタンスであるかどうかにかかわらず、具体的な実装と制御を注入することを強くお勧めします。

私は、テスト駆動開発の当時の比較的新しい実践を取り入れながら、約10年前に学んだテスト可能性の定義を使用することを選択します。

「クラスは、通常の環境から削除でき、すべての依存関係が注入されても機能する場合、テスト可能です。」

私が好むもう1つの選択は、コンポーネントとそれらが依存するコンポーネントとの相互作用をテストすることです。 コンポーネントへの依存関係を宣言する場合、コンポーネントがそれを正しく使用していることをテストしたいと思います。

IMHOは、適切に設計されたAPIを使用して、選択した方法で作業およびコーディングできます。いくつかの技術的な制約は許容されます。 HttpClientのようなコンポーネントの具体的な実装しかないので、選択の余地がありません。

そのため、これらの場合は通常のソリューションに頼って、インターフェイスを提供し、選択した方法で作業できる軽量のアダプター/ラッパーライブラリを作成するという選択肢が残されています。

私もパーティーに少し遅れていますが、これまでのコメントを全部読んだ後でも、どうすればいいのかわかりません。 テスト対象のオブジェクトの特定のメソッドがパブリックAPIの特定のエンドポイントを呼び出すことを確認する方法を教えてください。 何が戻ってくるかは気にしない。 誰かが私のApiClientクラスのインスタンスで「GoGetSomething」を呼び出すときに、「 https://somenifty.com/api/something 」にGETを送信することを確認したいだけです。 私がやりたいことは(コードが機能していない):

Mock<HttpClient> mockHttpClient = new Mock<HttpClient>();
mockHttpClient.Setup(x => x.SendAsync(It.IsAny<HttpRequestMessage>())).Verifiable();
ApiClient client = new ApiClient(mockHttpClient.Object);

Something response = await client.GoGetSomething();
mockHttpClient
    .Verify(x => x.SendAsync(
        It.Is<HttpRequestMessage>(m =>
            m.Method.Equals(HttpMethod.Get) &&
            m.RequestUri.Equals("https://somenifty.com/api/something"))));

ただし、SendAsyncをモックすることはできません。

その基本的なことを機能させることができれば、クラスがその呼び出しからの特定のRETURNSをどのように処理するかを実際にテストしたいと思うかもしれません。

何か案は? これは(私には)テストしたいほどの手に負えないもののようには思えません。

ただし、SendAsyncをモックすることはできません。

HttpClient.SendAsyncメソッドをモックしないでください。

代わりに、 HttpMessageHandlerから派生したモックハンドラーを作成します。 次に、そのSendAsyncメソッドをオーバーライドします。 次に、そのハンドラオブジェクトをHttpClientのコンストラクタに渡します。

@ jdcrutchley 、@ richardszalayのMockHttpでそれを行うことができます。

public class MyApiClient
{
    private readonly HttpClient httpClient;

    public MyApiClient(HttpMessageHandler handler)
    {
        httpClient = new HttpClient(handler, disposeHandler: false);
    }

    public async Task<HttpStatusCode> GoGetSomething()
    {
        var response = await httpClient.GetAsync("https://somenifty.com/api/something");
        return response.StatusCode;
    }
}

[TestMethod]
public async Task MyApiClient_GetSomething_Test()
{
    // Arrange
    var mockHandler = new MockHttpMessageHandler();
    mockHandler.Expect("https://somenifty.com/api/something")
        .Respond(HttpStatusCode.OK);

    var client = new MyApiClient(mockHandler);

    // Act
    var response = await client.GoGetSomething();

    // Assert
    Assert.AreEqual(response, HttpStatusCode.OK);
    mockHandler.VerifyNoOutstandingExpectation();
}

@jdcrutchleyまたは、1つのメソッドSendAsync ..を使用して独自のラッパークラスとラッパーインターフェイスを作成します。このメソッドは、(ラッパークラス内の)_real_ httpClient.SendAsyncを呼び出すだけです。

@richardszalayのMockHttpがやっていることだと思います。

これは、何かをモックできないときに人々が行う一般的なパターンです(インターフェイスがないか、仮想ではないため)。

mockhttpは、HttpMessageHandler上のモックDSLです。 Moqを使用して動的にモックするのと同じですが、DSLのおかげで意図がより明確になります。

エクスペリエンスはどちらの方法でも同じです。HttpMessageHandlerをモックし、そこから具体的なHttpClientを作成します。 ドメインコンポーネントは、HttpClientまたはHttpMessageHandlerのいずれかに依存します。

編集:TBH、既存の設計の問題が何であるか完全にはわかりません。 「mockableHttpClient」と「mockableHttpMessageHandler」の違いは、文字通りテストの1行のコード: new HttpClient(mockHandler)であり、実装が「プラットフォームごとの異なる実装」と「テストのための異なる実装」(つまり、テストコードの目的で汚染されていない)。

MockHttpのようなライブラリの存在は、よりクリーンでドメイン固有のテストAPIを公開するためだけに役立ちます。 インターフェイスを介してHttpClientをモック可能にしても、これは変わりません。

HttpClient APIの初心者として、私の経験が議論に価値をもたらすことを願っています。

また、HttpClientが簡単なモック用のインターフェイスを実装していないことを知って驚いた。 私はWebを検索し、このスレッドを見つけて、先に進む前に最初から最後まで読んでいました。 @ drub0yからの回答により、HttpMessageHandlerを単にモックするというルートを進むように私は確信しました。

私が選んだモックフレームワークはMoqで、おそらく.NET開発者にとってはかなり標準的なものです。 保護されたSendAsyncメソッドのセットアップと検証を試みてMoqAPIとの戦いに1時間近く費やした後、私はついに保護されたジェネリックメソッドを検証することでMoqのバグに遭遇しました。 ここを参照してください。

私はこの経験を共有して、バニラIHttpClientインターフェイスが生活をはるかに楽にし、数時間を節約したという議論を支持することにしました。 行き止まりになっているので、HttpClientの周りに単純なラッパー/インターフェイスの組み合わせを作成した開発者のリストに私を追加できます。

それはケンの優れた点です。HttpMessageHandler.SendAsyncが保護されていることを忘れていました。

それでも、API設計は最初にプラットフォームの拡張性に焦点を合わせており、HttpMessageHandlerのサブクラスを作成することは難しくありません(例としてmockhttpを参照)。 それは、よりモックフレンドリーな抽象的なパブリックメソッドへの呼び出しを渡すサブクラスでさえあり得ます。 私はそれが「不純」に見えることを理解していますが、テスト可能性と汚染-API-surface-for-testing-目的を永遠に議論することができます。

インターフェイスの+1。

他の人と同じように、私はHttpClientの初心者であり、クラスでそれを必要とし、ユニットテストの方法を探し始め、このスレッドを発見しました-そして今、数時間後、テストするために実装の詳細について学ぶ必要がありました。 非常に多くの人がこの道を旅しなければならなかったという事実は、これが人々にとって本当の問題であることの証拠です。 そして、声を上げた数人のうち、何人が黙ってスレッドを読んでいますか?

私は会社で大規模なプロジェクトを主導している最中であり、この種のウサギの道を追いかける時間がありません。 インターフェースは私に時間と会社のお金を節約してくれたでしょう-そしてそれはおそらく他の多くの人にとっても同じです。

私はおそらく自分のラッパーを作成するルートに行きます。 外から見ると、なぜHttpMessageHandlerをクラスに注入したのかは誰にもわかりません。

HttpClientのインターフェイスを作成できない場合、おそらく.netチームはインターフェイスを備えたまったく新しいhttpソリューションを作成できますか?

@ scott-martin、 HttpClientは何もせず、リクエストをHttpMessageHandlerに送信するだけです。

HttpClientをテストする代わりに、 HttpMessageHandlerをテストしてモックすることができます。 非常にシンプルで、 richardszalaymockhttpを使用することもできます。

@ scott-martinは、ほんの少しのコメントで言及されたように、HttpMessageHandlerを挿入しません。 ユニットテストを行う場合は、HttpClientを挿入し、HttpMessageHandlerを使用して_that_を挿入します。

@richardszalayのヒントに感謝します。これは、低レベルの抽象化を実装から除外するのに役立ちます。

@ YehudahA 、HttpMessageHandlerをモックすることで動作させることができました。 しかし、「それを機能させる」ことは私がチャイムを鳴らした理由ではありません。 @ PureKromeが言ったように、これは発見能力の問題です。 インターフェースがあれば、それをモックして、ほんの数分で途中にいることができたでしょう。 ないので、解決策を見つけるのに数時間を費やさなければなりませんでした。

インターフェイスを提供する際の制限と躊躇を理解しています。 コミュニティのメンバーとして、インターフェースが気に入っているというフィードバックをお伝えしたいと思います。インターフェースの数を増やしてください。インターフェースの操作やテストがはるかに簡単です。

これは発見能力の問題です。 インターフェースがあれば、それをモックして、ほんの数分で途中にいることができたでしょう。 ないので、解決策を見つけるのに数時間を費やさなければなりませんでした。

:arrow_up:これ。 これがすべてです。

編集:強調、私の。

私はそれがより発見可能である可能性があることに完全に同意しますが、それが「数時間」かかる可能性があるかどうかはわかりません。 「モックhttpclient」をグーグルすると、最初の結果としてこのStack Overflowの質問が表示されます。この質問では、最も評価の高い2つの回答(ただし、承認された回答ではなく、許可された回答)がHttpMessageHandlerを記述しています。

NP-私たちはまだ同意しないことに同意します。 すべて良いです:)

mscorlibがすべての荷物を持って戻ってくるので、この問題を閉じることができます:)

これらのhttpclientクラスにインターフェイスを実装させることはできますか? その後、私たち自身で残りを処理することができます。

DIサービスで使用されるHttpClientをモックするためにどのソリューションを提案しますか?
FakeHttpClientHandlerを実装しようとしていますが、SendAsyncメソッドをどうすればよいかわかりません。

別のマイクロサービスからIPベースの位置情報を取得するためにマイクロサービスを呼び出す単純なGeoLocationServiceがあります。 偽のHttpClientHandlerをサービスに渡します。このサービスには2つのコンストラクターがあり、1つはlocationServiceAddressを受け取り、もう1つはlocationServiceAddressと追加のHttpClientHandlerを受け取ります。

public class GeoLocationService: IGeoLocationService {

        private readonly string _geoLocationServiceAddress;
        private HttpClient _httpClient;
        private readonly object _gate = new object();

        /// <summary>
        /// Creates a GeoLocation Service Instance for Dependency Injection
        /// </summary>
        /// <param name="locationServiceAddress">The GeoLocation Service Hostname (IP Address), including port number</param>
        public GeoLocationService(string locationServiceAddress) {
            // Add the ending slash to be able to GetAsync with the ipAddress directly
            if (!locationServiceAddress.EndsWith("/")) {
                locationServiceAddress += "/";
            }

            this._geoLocationServiceAddress = locationServiceAddress;
            this._httpClient = new HttpClient {
                BaseAddress = new Uri(this._geoLocationServiceAddress)
            };
        }

        /// <summary>
        /// Creates a GeoLocation Service Instance for Dependency Injection (additional constructor for Unit Testing the Service)
        /// </summary>
        /// <param name="locationServiceAddress">The GeoLocation Service Hostname (IP Address), including port number.</param>
        /// <param name="clientHandler">The HttpClientHandler for the HttpClient for mocking responses in Unit Tests.</param>
        public GeoLocationService(string locationServiceAddress, HttpClientHandler clientHandler): this(locationServiceAddress) {
            this._httpClient.Dispose();
            this._httpClient = new HttpClient(clientHandler) {
                BaseAddress = new Uri(this._geoLocationServiceAddress)
            };
        }

        /// <summary>
        /// Geo Location Microservice Http Call with recreation of HttpClient in case of failure
        /// </summary>
        /// <param name="ipAddress">The ip address to locate geographically</param>
        /// <returns>A <see cref="string">string</see> representation of the Json Location Object.</returns>
        private async Task<string> CallGeoLocationServiceAsync(string ipAddress) {
            HttpResponseMessage response;
            string result = null;

            try {
                response = await _httpClient.GetAsync(ipAddress);

                response.EnsureSuccessStatusCode();

                result = await response.Content.ReadAsStringAsync();
            }
            catch (Exception ex) {
                lock (_gate) {
                    _httpClient.Dispose(); 
                    _httpClient = new HttpClient {
                        BaseAddress = new Uri(this._geoLocationServiceAddress)
                    };
                }

                result = null;

                Logger.LogExceptionLine("GeoLocationService", "CallGeoLocationServiceAsync", ex.Message, stacktrace: ex.StackTrace);
            }

            return result;
        }

        /// <summary>
        /// Calls the Geo Location Microservice and returns the location description for the provided IP Address. 
        /// </summary>
        /// <param name="ipAddress">The <see cref="string">IP Address</see> to be geographically located by the GeoLocation Microservice.</param>
        /// <returns>A <see cref="string">string</see> representing the json object location description. Does two retries in the case of call failure.</returns>
        public async Task<string> LocateAsync(string ipAddress) {
            int noOfRetries = 2;
            string result = "";

            for (int i = 0; i < noOfRetries; i ++) {
                result = await CallGeoLocationServiceAsync(ipAddress);

                if (result != null) {
                    return result;
                }
            }

            return String.Format(Constants.Location.DefaultGeoLocationResponse, ipAddress);
        }

        public void Dispose() {
            _httpClient.Dispose();
        }

    }

テストクラスでは、SendAsyncメソッドをオーバーライドするHttpClientHandlerを拡張するFakeClientHandlerという名前の内部クラスを記述したいと思います。 別の方法は、モックフレームワークを使用してHttpClientHandlerをモックすることだと思います。 どうすればいいのかまだわかりません。 あなたが助けることができれば、私はあなたの借金に永遠になります。

public class GeoLocationServiceTests {

        private readonly IConfiguration _configuration;
        private IGeoLocationService _geoLocationService;
        private HttpClientHandler _clientHandler;

        internal class FakeClientHandler : HttpClientHandler {

            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {

                // insert implementation here

                // insert fake responses here. 
                return (Task<HttpResponseMessage>) null;
            }

        }

        public GeoLocationServiceTests() {
            this._clientHandler = new FakeClientHandler();
            this._clientHandler.UseDefaultCredentials = true;
            this._geoLocationService = new GeoLocationService("http://fakegeolocation.com/json/", this._clientHandler);
        }


        [Fact]
        public async void RequestGeoLocationFromMicroservice() {
            string ipAddress = "192.36.1.252";

            string apiResponse = await this._geoLocationService.LocateAsync(ipAddress);
            Dictionary<string, string> result = JsonConvert.DeserializeObject<Dictionary<string,string>>(apiResponse);

            Assert.True(result.ContainsKey("city"));

            string timeZone;
            result.TryGetValue("time_zone", out timeZone);

            Assert.Equal(timeZone, @"Europe/Stockholm");
        }

        [Fact]
        public async void RequestBadGeoLocation() {
            string badIpAddress = "asjldf";

            string apiResponse = await this._geoLocationService.LocateAsync(badIpAddress);
            Dictionary<string, string> result = JsonConvert.DeserializeObject<Dictionary<string, string>>(apiResponse);

            Assert.True(result.ContainsKey("city"));

            string city;
            result.TryGetValue("city", out city);
            Assert.Equal(city, "NA");

            string ipAddress;
            result.TryGetValue("ip", out ipAddress);
            Assert.Equal(badIpAddress, ipAddress);
        }
    }

@danielmihaiHttpMessageHandlerは事実上「実装」です。 通常、HttpClientを作成し、ハンドラーをコンストラクターに渡してから、サービスレイヤーをHttpClientに依存させます。

オーバーライド可能なSendAsyncが保護されているため、一般的なモックフレームワークを使用することは困難です。 私は特にその理由でmockhttpを作成しました(ただし、ドメイン固有のモッキング機能があると便利です。

@danielmihaiリチャードが言ったことは、私たち全員がHttpClientをあざけることで今耐えなければならない_痛み_をかなり要約しています。

HttpClientをモックアウトするのに役立つ_another_ライブラリとしてHttpClient.Helpersもあります。具体的には、応答を偽造するための偽のHttpMessageHandlerを提供します。

私はそれを理解し、このようなものを書きました... [私は「痛み」に対処したと思います]

internal class FakeClientHandler : HttpClientHandler {
            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
                if (request.Method == HttpMethod.Get) {
                    if (request.RequestUri.PathAndQuery.Contains("/json/")) {
                        string requestPath = request.RequestUri.PathAndQuery;
                        string[] splitPath = requestPath.Split(new char[] { '/' });
                        string ipAddress = splitPath.Last();

                        // RequestGeoLocationFromMicroservice
                        if (ipAddress.Equals(ipAddress1)) {
                            var response = new HttpResponseMessage(HttpStatusCode.OK);
                            response.Content = new StringContent(response1);

                            return Task.FromResult(response);
                        }

                        // RequestBadGeoLocation
                        if (ipAddress.Equals(ipAddress2)) {
                            var response = new HttpResponseMessage(HttpStatusCode.OK);
                            response.Content = new StringContent(response2);
                            return Task.FromResult(response);
                        }
                    }
                }
                return (Task<HttpResponseMessage>) null;
            }
        }

すべてのテストに合格

より良い方法がある場合は、実装についてコメントしてください。

ありがとう!

これに私の考えを1年以上前の議論に加えます。

@SidharthNabar

まず、各リクエストを送信するためにHttpClientの新しいインスタンスを作成していることに気付きました。これはHttpClientの意図されたデザインパターンではありません。 1つのHttpClientインスタンスを作成し、それをすべてのリクエストに再利用すると、接続プールとメモリ管理を最適化するのに役立ちます。 HttpClientの単一インスタンスを再利用することを検討してください。 これを行うと、偽のハンドラーをHttpClientの1つのインスタンスに挿入するだけで完了です。

usingステートメントを使用することになっていないが、HttpClientインスタンスをnew使用することになっていない場合、たとえばコンシューマーのコンストラクターで、注入されていないインスタンスをどこで破棄しますか? HttpClientインスタンスに挿入するために構築しているHttpMessageHandlerはどうですか?

完全なインターフェースかどうかは別として(私はすべてインターフェースについてです)、少なくともドキュメントには、テスト目的でHttpMessageHandlerを挿入できると記載されている可能性があります。 はい、コンストラクターについて言及されています。はい、独自のHttpMessageHandlerを挿入できると述べられていますが、なぜこれを実行したいかについてはどこにも記載されていません。

@richardszalay

私はそれがより発見可能である可能性があることに完全に同意しますが、それが「数時間」かかる可能性があるかどうかはわかりません。 「モックhttpclient」をグーグルすると、最初の結果としてこのStack Overflowの質問が表示されます。この質問では、最も評価の高い2つの回答(ただし、承認された回答ではなく、許可された回答)がHttpMessageHandlerを記述しています。

APIは、ドキュメントIMHOを閲覧するときに直感的に使用できる必要があります。 GoogleやSOに質問しなければならないのは確かに直感的ではありません。リンクされた質問は、.NET Coreが最新性を明らかにする前にさかのぼります(質問と_new_フレームワークのリリースの間に何かが変更された可能性がありますか?)

コード例を追加し、ヘルパーライブラリを提供してくれたすべての人に感謝します!

つまり、http呼び出しを行う必要のあるクラスがあり、そのクラスを単体テストする場合は、そのhttpクライアントをモックできる必要があります。 httpクライアントはインターフェースされていないため、そのクライアントのモックを挿入することはできません。 代わりに、オプションのパラメーターとしてメッセージハンドラーを受け入れるhttpクライアントのラッパー(インターフェイスを実装する)を作成し、それを偽のハンドラーに送信して挿入する必要があります。 それは、無料でジャンプするための余分なフープです。 別の方法は、クラスにHttpClientではなくHttpMessageHandlerのインスタンスを取得させることです。

私は髪を裂くつもりはありません、それはただぎこちなく感じて、少し直感的ではないようです。 これがとても人気のあるトピックであるという事実は、それをサポートしていると思います。

httpクライアントはインターフェースされていないため、コンテナーを挿入することはできません。

@dasjestyrインターフェースなしで依存性注入を行うことができます。 インターフェイスは多重継承を可能にします。 依存性注入の要件ではありません。

ええ、DIはここでは問題ではありません。 モックアウトするのがいかに簡単/いいかなどです。

@shaunluttinインターフェースなしでDIを実行できることは承知していますが、それ以外のことを暗示していません。 HttpClientに依存しているクラスについて話しています。 クライアントのインターフェイスがないと、モックされたhttpクライアントまたは任意のhttpクライアントを挿入できません(実際のhttp呼び出しを行わずに単独でテストできるようにするため)。 HttpClientのそれを模倣するように定義します。 あなたが一文に固執していた場合に備えて、わかりやすくするためにいくつかの言い回しを少し変更しました。

その間、私はユニットテストで偽のメッセージハンドラーを作成し、それを注入しています。つまり、クラスはハンドラーを注入することを中心に設計されています。これは世界で最悪のことではないと思いますが、ぎこちなく感じます。 そして、テストにはモック/スタブの代わりに偽物が必要です。 キモい。

また、学術的に言えば、インターフェースは多重継承を有効にせず、それを模倣することしかできません。 インターフェイスを継承せず、実装します。インターフェイスを実装することによって機能を継承するのではなく、何らかの方法で機能を実装したことを宣言するだけです。 それ以外の場合は、多重継承と他の場所で作成した既存の機能を模倣するために、インターフェイスを実装し、継承ではなく実際の構成である実際の機能(アダプターなど)を保持する内部メンバーに実装をパイプすることができます。

その間、私はユニットテストで偽のメッセージハンドラーを作成し、それを注入しています。つまり、クラスはハンドラーを注入することを中心に設計されています。これは世界で最悪のことではないと思いますが、ぎこちなく感じます。 そして、テストにはモック/スタブの代わりに偽物が必要です。 キモい。

これはこの問題の核心/核心です->そしてそれも_純粋に_意見があります。

フェンスの片側: <insert their rational here>のため、偽のメッセージハンドラー
フェンスの反対側: <insert their rational here>のため、インターフェース(または仮想メソッドですが、それでもPITAのほんの一部です)。

つまり、偽物の設定には多くの余分な作業が必要であり、多くの場合、テストのためだけに他のクラス全体を作成することになりますが、実際にはそれほど価値はありません。 スタブは、実際の実装なしでテストを続行できるため、優れています。 モックはアサーションに使用できるため、私が本当に興味を持っているものです。 たとえば、このインターフェイスの実装のメソッドが、このテストの現在の設定でx回呼び出されたことを表明します。

私は実際に私のクラスの1つでそれをテストしなければなりませんでした。 そのためには、SendAsync()メソッドが呼び出されるたびにインクリメントするアキュムレータを偽のハンドラに追加する必要がありました。 ありがたいことに、それは単純なケースでしたが、さあ。 現時点では、モックは実質的に標準です。

うん-ここであなたに完全に同意します👍🍰

マイクロソフト、 IHttpClientの期間を追加してください。 または、一貫性を保ち、BCL内のすべてのインターフェイスを削除します- IListIDictionaryICollectionとにかく「バージョン管理が難しい」ため、 ちなみに、 IEnumerableも削除します。

深刻なことに、「独自のハンドラーを作成するだけでよい」(確かに、インターフェイスはより簡単でクリーンです)や「イ​​ンターフェイスは重大な変更を導入します」(とにかくすべての.netバージョンでそれを行います)のように主張する代わりに、単にインターフェースを追加し、開発者がそれをどのようにテストしたいかを決定できるようにします。

@lasseschou IListIDictionaryICollection 、およびIEnumerableは、それらを実装するクラスが多種多様であるため、絶対に重要です。 IHttpClientHttpClientによってのみ実装されます-ロジックとの一貫性を保つために、BCLのすべてのクラスには、モックが必要になる可能性があるため、インターフェイスタイプを指定する必要があります。
このような雑然としたものを見るのは悲しいことです。

劇的なトレードオフがない限り、 IHttpClientはとにかく嘲笑するのに間違った抽象化レベルです。 アプリは、ほぼ確実にHttpClientのすべての機能を実行しているわけではありません。 アプリの特定のリクエストに関して、 HttpClientを独自のサービスでラップし、独自のサービスをモックすることをお勧めします。

@ jnm2-興味深いことに、なぜIHttpClientが間違った抽象化レベルであると思いますか?

私は嫌いでもトローリングでもありません-正直なQ。

(学ぶのが好きなど)。

クライアントであるため、ほぼ完璧なレベルの抽象化を考慮して、同じことを尋ねるつもりでした。

絶対ではありません。 Webブラウザーを構築している場合、アプリが必要とするものとHttpClientが抽象化するものが1:1であることがわかります。 ただし、HttpClientを使用して何らかのAPI(HTTPに加えてより具体的な期待値を持つもの)と通信する場合は、HttpClientで実行できることのほんの一部しか必要ありません。 HttpClientトランスポートメカニズムをカプセル化してそのサービスをモックするサービスを構築すると、アプリの残りの部分は、HttpClientに対してコーディングするのではなく、サービスのAPI固有のインターフェイス抽象化に対してコーディングできます。

また、私は「自分が所有していないタイプをモックしない」という原則に同意しているので、YMMVです。
従来のテストとモックを比較するhttp://martinfowler.com/articles/mocksArentStubs.htmlも参照してください。

ここでのポイントは、HttpClientがこれほど大きな進歩であるということです。
WebClient、XMLHttpRequest、それがHTTPを行うための好ましい方法になったこと
呼び出します。 清潔でモダンです。 私はいつも話すモック可能なアダプターを使用します
HttpClientに直接送信するため、コードのテストは簡単です。 しかし、私はしたい
それらのアダプタクラスもテストします。 そしてそれは私が発見したときです:ありません
IHttpClient。 おそらくあなたは正しいです、それは必要ではありません。 おそらくあなたはそれが
抽象化のレベルが間違っています。 しかし、それは本当に便利ではありません
すべてのプロジェクトでさらに別のクラスにラップする必要があります。 見えない
そのような重要なクラスのための別個のインターフェースを持たない理由。

ラッセ・シュー
創設者兼CEO

http://mouseflow.com
[email protected]

https://www.facebook.com/mouseflowdotcom/
https://twitter.com/mouseflow
https://www.linkedin.com/company/mouseflow

守秘義務の通知:この電子メールメッセージは、添付ファイルを含めて、
意図された受信者の唯一の使用のためであり、機密情報が含まれている可能性があります。
法律で保護されている専有情報および/または特権情報。 どれでも
不正なレビュー、使用、開示、配布は禁止されています。 もし、あんたが
意図した受信者ではない場合は、返信メールで送信者に連絡してください
元のメッセージのすべてのコピーを破棄します。

2016年11月12日14:57、Joseph [email protected]
書きました:

絶対ではありません。 あなたがウェブブラウザを構築しているなら、私は見ることができます
アプリに必要なものとHttpClientが抽象化するものが1:1である方法。 だが
HttpClientを使用して、ある種のAPIと通信している場合
HTTPに加えて、より具体的な期待があれば、必要なのは
HttpClientで実行できることの一部。 あなたがサービスを構築する場合
HttpClientトランスポートメカニズムをカプセル化し、そのサービスをモックします。
アプリの残りの部分は、サービスのAPI固有のインターフェースに対してコーディングできます
HttpClientに対してコーディングするのではなく、抽象化。


あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/dotnet/corefx/issues/1624#issuecomment -260123552、またはミュート
スレッド
https://github.com/notifications/unsubscribe-auth/ACnGPp3fSriEJCAjXeAqMZbBcTWcRm9Rks5q9cXlgaJpZM4EPBHy

HttpClientは「柔軟性が高すぎて」、モックを作成するのに適切なレイヤーではないという奇妙な意見を聞いています。これは、1つのクラスに非常に多くの責任と公開された機能を持たせるという決定の副作用のようです。 しかし、それは人々がどのようにコードをテストしたいかとはまったく無関係です。

消費者がHttpClientの実装方法の詳細を知っていることを期待してカプセル化を破っているという理由だけで、これを修正することを本当に望んでいます(この場合、意味のある機能を追加しないラッパーであることを「知る」ため)使用している場合と使用していない場合がある別のクラスをラップします)。 フレームワークの利用者は、どのインターフェースが公開されているか以外に実装について何も知る必要はありません。ここでの多くの合理化は、回避策があり、クラスに精通していることで、カプセル化されていない多くの方法が提供されることです。モック/スタブ機能。

修正してください!

これに偶然出くわしたので、私の2cを投げ入れました...

HttpClientをあざけるのはばかげていると思います。

モックするSendAsyncは1つだけですが、 HttpResponseMessageHttpResponseHeadersHttpContentHeadersなどには、非常に多くのニュアンスがあります。早く乱雑になり、おそらく間違った振る舞いをしているでしょう。

自分? OwinHttpMessageHandlerを使用して

  1. サービスの外部からHTTP受け入れテストを実行します。
  2. _Dummy_ HTTPサービスを、そのサービスへのアウトバウンドHTTPリクエストを作成するコンポーネントに挿入します。 つまり、私のコンポーネントには、 HttpMessageHandlerのオプションのctorパラメーターがあります。

同様のことを行うMVC6ハンドラーがあると確信しています。

_人生は大丈夫です。_

@damianh :dotnetコアを使用している人がHttpClientのようなクラスをテストするために、Owinで何かをする必要があるのはなぜですか? これは特定のユースケース(OWINでホストされているAspCore)にとっては非常に理にかなっているかもしれませんが、これはそれほど一般的ではないようです。

これは、「ユーザーの期待と一致するHttpClientの単体テストの最も一般的な方法」と「統合テストの方法」の懸念を実際に混同しているようです:)。

Oh _testing_ HttpClient ...トピックは、
HttpClientへの依存。 私を気にしないでください。 :)

2016年12月5日午後7時56分、「brphelps」 [email protected]は次のように書いています。

@damianh https://github.com/damianh :ドットネットコアを使用する必要があるのはなぜですか
HttpClientのようなクラスをテストするためにOwinで何かをする必要がありますか? これはかもしれません
特定のユースケース(OWINでホストされているAspCore)には多くの意味がありますが、
これはそれほど一般的ではないようです。

これは本当に「最も一般的なものは何か」の懸念を混乱させているようです
ユーザーの期待とも一致するHttpClientの単体テストへの方法」
「統合テストを行うにはどうすればよいですか」:)。


あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/dotnet/corefx/issues/1624#issuecomment-264942511 、またはミュート
スレッド
https://github.com/notifications/unsubscribe-auth/AADgXJxunxBgFGLozOsisCoPqjMoch48ks5rFF5vgaJpZM4EPBHy

@ damianh-依存関係があるものですが、ソリューションには他にも多くの依存関係が含まれているようです。 渡されたHttpClientインスタンス(またはDIされたインスタンス)を使用するクラスライブラリを想像しています-Owinはどこに登場しますか?

私が見せようとしていたのは、おもしろいことではありません。
.net core / netstandardでも動作しますので、それに集中しないでください。

興味深いのは、インプロセスを作成するHttpMessageHandlerです。
ダミーのHTTPエンドポイントに対するメモリ内HTTPリクエスト。

Aspnet CoreのTestServerは、以前のkatanaと同様に、同じように機能します。
仕方。

したがって、テスト対象のクラスにはHttpClient DIがあり、_it_には
HttpMessageHandlerが直接注入されました。 テスト可能で
アサート可能な表面があります。

テストダブルの特定の形式であるモックは、私がまだアドバイスしたいことです
HttpClientに対して。 したがって、IHttpClientは必要ありません。

2016年12月5日午後8時15分、「brphelps」 [email protected]は次のように書いています。

@damianhhttps ://github.com/damianh-それは
依存関係がありますが、ソリューションには他の多くの依存関係が含まれているようです
写真でも。 たまたま使用するクラスライブラリを想像しています
HttpClientインスタンス(またはDIされたインスタンス)で渡される-Owinはどこに入るのか
絵?


あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/dotnet/corefx/issues/1624#issuecomment-264947538 、またはミュート
スレッド
https://github.com/notifications/unsubscribe-auth/AADgXEESfQj9NnKLo8LucFLyajWtQXKBks5rFGK8gaJpZM4EPBHy

@ damianh-おもしろいことを言っていることを完全に理解します-しかし、人々はスマートモックを備えたダミーのHTTPエンドポイントを必要としません-例えば@richardszalayのMockHttpライブラリ。

少なくともユニットテストではありません:)。

場合によります。 オプションは良いです:)

2016年12月5日午後8時39分、「brphelps」 [email protected]は次のように書いています。

@damianhhttps ://github.com/damianh-あなたが何であるかを完全に理解する
おもしろいのは-でも、ダミーのHTTPは必要ない
スマートモックを使用したエンドポイント-例:@ richardszalay
https://github.com/richardszalayのMockHttpライブラリ。

少なくともユニットテストではありません:)。


あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/dotnet/corefx/issues/1624#issuecomment-264954210 、またはミュート
スレッド
https://github.com/notifications/unsubscribe-auth/AADgXOl4UEDGYCVLpbvwhueaK52VtjH6ks5rFGhlgaJpZM4EPBHy

@damianhは言った:
オプションは良いです:)

うん-それに完全に同意することができます👍これ、IMO ..私たちは今持っていません😢**

独自のHMHを作成して注入する理由を完全に理解しています。 それが私が今していることです。 _残念ながら_、ユニットテストの懸念事項のために2番目のctorを作成する(またはオプションのctorパラメータを提供する)必要があります。 その文についてもう一度考えてみてください->私は完全に可能なユーザーオプションを作成しています...これは実際にはユニットテストで_何かを行う_ことを可能にするためだけのものです。 私のクラスのエンドユーザー/消費者は、そのctorオプションを_実際に_使用することは決してありません...私には匂いがします。

オプションについてのポイントに戻る->今のところ私はオプションを持っているような気がしません。 HttpClientの内部について_持っている_から_学ぶ_。 文字通りボンネットを開き、ボンネットの下を見てください。 (私は幸運にもTL; DRを入手できました。SirFowlerTheAwesome™️から研究時間を短縮しました)。

それで、インターフェースがないというあなたのポイントで、インターフェースが悪いことになるときの例をいくつか挙げてください。 など。覚えておいてください->私は常にインターフェースを小規模/単純から中規模の例の_オプション_として宣伝してきました...すべての例の100%ではありません。

私はあなたのダミアンをトロールしたり攻撃したりしようとはしていません-ただ理解しようとしています。

* Ninja Edit:技術的には、実際にはオプションがあります->何もできません/独自のラッパーを作成します/など。つまり、物事を単純化するオプションは多くありません

ねえ@PureKrome 、経験を共有するだけの心配はありません。

「ユニットテスト」という用語は何度も使用されていますが、これについて少し考えてみましょう。 同じページにいるので、$ HttpClient (またはHMH )にctor依存関係があるFooClassがあり、$ HttpClientを_モック_したいとします。

ここで実際に言っているのは、「 FooClassは、任意のURL(プロトコル/ホスト/リソース)、任意のメソッド、任意のコンテンツタイプ、任意の転送エンコーディング、任意のコンテンツエンコーディング、_anyを使用してHTTPリクエストを作成できるということです。リクエストheaders_、および任意の応答コンテンツタイプ、任意のステータスコード、Cookie、リダイレクト、転送エンコーディング、キャッシュ制御ヘッダーなど、およびネットワークタイムアウト、タスクキャンセル例外などを処理できます。」

HttpClient神オブジェクトのようなものです; あなたはそれでたくさんすることができます。 HTTPは、プロセスを超えて到達するリモートサービス呼び出しでもあります。

これは本当に単体テストですか? 今正直に:)

** Ninja Editも:サーバーからjsonを取得したい場所でFooClassユニットをテスト可能にしたい場合は、 Func<CancellationToken, Task<JObject>>などにctor依存関係があります。

インターフェースが悪い場合の例をいくつか挙げてください。

言わなかった! IHttpClientは、A)代替の具体的な実装に置き換えられず、B)前述のように神オブジェクトの懸念があるため、役に立たないと言いました。

もう一つの例? IAssembly

@ damianh- 「神のクラス」のステータスは、発信者には関係ありません。 HttpClientが神のクラスである/そうでないことを確認するのは呼び出し元の責任ではありません。 彼らはそれがHTTP呼び出しを行うために使用するオブジェクトであることを知っています:)。

それで、.NETフレームワークが神のクラスを私たちに公開したことを受け入れると仮定すると...どうすれば私たちの懸念を減らすことができますか? どうすればそれを最もよくカプセル化できますか? それは簡単です-従来のモックフレームワーク(Moqは素晴らしい例です)を使用し、HttpClient実装を完全に分離します。これは、人々がそれを消費するときに、内部でどのように設計されているかを気にしないためです。 私たちはそれをどのように使うかだけを気にします。 ここでの問題は、従来のアプローチが機能しないことです。その結果、人々が問題を解決するためのより多くの興味深い解決策を考え出すのを目にします:)。

文法と明快さのためのいくつかの編集:D

@damianhは、 HttpClientが超神のクラスのようなものであることに完全に同意します。

また、何が入って何が出てくるかについて_非常に多くの変化_が存在する可能性があるため、なぜ内部のすべてについてもう一度知る必要があるのか​​わかりません。

例:APIから株式を取得したい。 https://api.stock-r-us.com/aapl 。 結果はいくつかのJSONペイロッドです。

そのため、そのエンドポイントを呼び出そうとすると失敗する可能性があります。

  • エンドポイントのURLが間違っていますか?
  • 間違ったスキーム?
  • 特定のヘッダーがありませんか?
  • 不正なヘッダー
  • 不正なリクエストペイロード(おそらくこれは何らかの本文を含むPOSTですか?)
  • ネットワークエラー/タイムアウトなど
    ..。

したがって、これらのことをテストしたい場合、HMHは、実際のHMHを乗っ取って、私が言ったことを置き換えて戻るため、これを行うための優れた方法であることを理解しています。

しかし、-私が(自分の責任で)これらすべてのことを無視して、次のように言いたい場合はどうでしょうか。
_呼び出してエンドポイントを作成したいとします。すべてがOKで、素晴らしいjsonペイロードの結果が得られます。_
これと同じくらい簡単なことをするために、私はまだこれらすべてのものについて学び、心配する必要がありますか?

または...これは悪い考え方ですか。 ハッキーすぎるので、このように考えるべきではありませんか? 安すぎる? エラーが発生しやすいですか?

ほとんどの場合、私はエンドポイントを呼び出すだけです。 ヘッダーやキャッシュ制御、ネットワークエラーなどは考えていません。 私は通常、VERB + URL(および必要に応じてPAYLOAD)を考えてから、結果について考えます。 これを回避してCatch-All-The-Things:tm:にエラー処理を行い、何か悪いことが起こったために敗北を認めます:(

だから私は問題を間違って見ている(つまり、私はまだ私のn00bステータスを振ることができない)-または-この神クラスは私たちにユニットテストの悲しみの山を引き起こしています。

HTTPは、プロセスを超えて到達するリモートサービス呼び出しでもあります。
これは本当に単体テストですか? 今正直に:)

私にとって、 HttpClientは_依存サービス_です。 サービスが実際に何をするかは気にしません。コードベースの外部にある何かを行うものとして分類します。 したがって、外部電源で何かを実行したくないので、それを制御し、ハイジャックして、それらのサービス呼び出しの結果を判断したいと思います。 私はまだ_my_作業単位をテストしようとしています。 私の小さな論理。 確かにそれは他のものに依存するかもしれません。 宇宙全体を制御できるかどうかは、まだ単体テストです。このロジックは、他のサービスに依存/統合していない_内に存在します。

**別の編集:または多分-HttpClientが実際に神オブジェクトである場合、会話はそれを何かに減らすことについてである必要があります...より良いですか? それとも、それは神オブジェクトではないのでしょうか? (議論を開こうとしているだけです)。

残念ながら、単体テストの問題のために2番目のctorを作成する(またはオプションのctorパラメーターを提供する)必要があります。 その文についてもう一度考えてみてください->私は完全に可能なユーザーオプションを作成しています...これは実際には単体テストで何かを実行できるようにするためだけのものです。 私のクラスのエンドユーザー/消費者は、そのctorオプションを実際に使用することは決してありません...私には匂いがします

ここで何を言おうとしているのかわかりません。

「HttpMessageHandlerを受け入れる2番目のコンストラクターを持つのは好きではありません」...テストでそれをHttpClientでラップするようにします

「HttpClientへの依存関係を反転させずに単体テストを実行したい」...難しいですか?

@ richardszalay- @ PureKromeは、過度に意見の分かれるテスト戦略をサポートするためだけに設計を改善しない方法でコードを変更したくないという感情を持っていると思います。 誤解しないでください。私はあなたのライブラリについて不平を言っていません。私がより良い解決策を望んでいる唯一の理由は、dotnetコアがそれを必要としないことへの期待が高いからです=)。

私はこれをMockHttpの観点からは考えていませんが、一般的にテスト/モックを作成しています。
依存関係の逆転は、.NETでの単体テストの基本的な必要性であり、通常はctor-injectionが含まれます。 提案された代替案がどうなるかはわかりません。

確かに、HttpClientがIHttpClientを実装している場合でも、それを受け入れるコンストラクターは存在します...

確かに、HttpClientがIHttpClientを実装している場合でも、それを受け入れるコンストラクターは存在します...

同意しました。 私は文字通り目が覚めたばかりで、脳内の単一の細胞を生き返らせるのに苦労していたので、私は思考の中で「流れを横切っていました」。

誰かがまだ退屈していない場合は、私の「ctor」コメントplzを無視して、ディスカッションを続けてください😄

HttpClientを依存関係として使用すると、ユニットテストに実際につながる可能性があるかどうかという質問に戻ることができますか。

依存関係コンストラクターが注入されるクラスFooClassがあるとします。 ここで、 FooClassでメソッドの単体テストを行います。つまり、メソッドへの入力を指定し、 FooClassをその環境から可能な限り分離し、結果を期待値と照合します。 。 このすべてのパイプラインで、依存オブジェクトの内臓を気にしたくありません。 FooClassが存在する環境の種類を指定できれば、私にとって最適です。テスト対象のシステムへの依存関係を、必要なとおりに動作するようにモックしたいと思います。私のテストが機能するために。

テスト対象のシステムに注入された依存性( HttpClient )の依存性( HttpMessageHandler )を指定する必要があるため、現在、 HttpClientの実装方法に注意する必要があります。 直接の依存関係をモックアウトするのではなく、モック化された連鎖依存関係を使用して具体的な実装を作成する必要があります。

テーマ:内部がどのように機能するかを知らずにHttpClientをモックすることはできません。また、モックでクラスの内部を完全にバイパスすることはできないため、驚くべきことを何もしないように実装に依存する必要があります。 これは基本的にユニットテスト101に準拠していません-テストから無関係な依存関係コードを取得します=)。

あなたは多くの異なるコメントで存在するテーマを見ることができます:

「私にとって、HttpClientは依存型サービスです。サービスが実際に何をするかは気にせず、コードベースの外部にある何かを実行するものとして分類します。」-PureKrome

「現在、テスト対象のシステムに注入された依存関係(HttpClient)の依存関係(HttpMessageHandler)を指定する必要があるため、HttpClientの実装方法に注意する必要があります。」-Thaoden

「クライアントであるため、ほぼ完璧なレベルの抽象化を考慮して、同じことを尋ねるつもりでした。」 --dasjestyr

「HttpMessageHandlerは事実上「実装」です。通常はHttpClientを作成し、ハンドラーをコンストラクターに渡してから、サービスレイヤーをHttpClientに依存させます。」 --richardszalay

「しかし、重要な問題は残ります。偽のハンドラーを定義して、それをHttpClientオブジェクトの下に挿入する必要があります。」 --SidharthNabar

「HttpClientには、ユニットのテスト容易性に関する多くのオプションがあります。封印されておらず、そのメンバーのほとんどは仮想です。抽象化を簡素化するために使用できる基本クラスと、HttpMessageHandlerで慎重に設計された拡張ポイントがあります」-ericstj

「APIがモック可能でない場合は、修正する必要があります。ただし、インターフェースとクラスの関係はありません。すべての実用的な目的で、インターフェースはモック可能性の観点から純粋な抽象クラスと同じです。」 --KrzysztofCwalina

「ちなみに、オプションが独自の偽のメッセージハンドラーを作成する必要がある場合、これはコードを掘り下げないと明らかではありませんが、それは非常に高い障壁であり、多くの開発者にとって非常に苛立たしいものになります。」 --ctolkien

これは本当に単体テストですか? 今正直に:)

私は知らないよ。 テストはどのように見えますか?

私はその声明はちょっとしたストローマンだと思います。 重要なのは、httpクライアントである依存関係をモックして、コードをそれらの依存関係から分離して実行できるようにすることです。つまり、実際にはhttp呼び出しを行わないでください。 私たちがしているのは、インターフェースで期待されるメソッドが適切な条件下で呼び出されていることを確認することだけです。 より軽い依存関係を選択すべきかどうかは、まったく別の議論のようです。

HttpClientはこれまでで最悪の依存関係です。 それは文字通り
扶養家族は「インターネットを呼び出す」ことができます。 確かに、次はTCPをあざけるでしょう。 ふふ

2016年12月7日午前5時34分、「ジェレミースタッフォード」 [email protected]は次のように書いています。

これは本当に単体テストですか? 今正直に:)

私は知らないよ。 テストはどのように見えますか?

私はその声明はちょっとしたストローマンだと思います。 ポイントは、モックすることです
httpクライアントである依存関係。 私たちがやっていることは、
インターフェイスで期待されるメソッドが右側で呼び出されていました
条件。 より軽い依存関係を選択すべきかどうかは
まったく別の議論になること。


あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/dotnet/corefx/issues/1624#issuecomment-265353526 、またはミュート
スレッド
https://github.com/notifications/unsubscribe-auth/AADgXPL39vnUbWrUuUBtfmbjhk5IBkxHks5rFjdqgaJpZM4EPBHy

これは基本的にユニットテスト101に準拠していません-テストから無関係な依存関係コードを取得します=)。

個人的には、それがその特定の慣行の正しい解釈ではないと思います。 依存関係をテストしようとしているのではなく、コードがその依存関係とどのように相互作用するかをテストしようとしているので、無関係ではありません。 そして、非常に簡単に言えば、実際の依存関係を呼び出さずにコードに対して単体テストを実行できない場合、それは単独でテストすることではありません。 たとえば、その単体テストがインターネットにアクセスできないビルドサーバーで実行された場合、テストは失敗する可能性があります。したがって、それをモックする必要があります。 HttpClientへの依存関係を削除することもできますが、それは、呼び出しを行う他のクラスになってしまうことを意味します。おそらく、そのクラスについて話しているのでしょうか。 あなたは決して尋ねなかったのであなたは知りません。 それで、私のドメインサービスはHttpClientに依存する必要がありますか? いいえ、もちろん違います。 それは他の種類の抽象サービスに抽象化され、代わりにそれをあざけるでしょう。 さて、私が思いついた抽象化を注入するレイヤーでは、チェーンのどこかで、_something_はHttpClientについて知り、何かが私が書いた他の何かによってラップされ、他の何かが私のユニットテストを作成します。

したがって、ある種のメディエーターの単体テストで、インターネット上のサービスへの呼び出しが失敗すると、コードが何らかの形式のトランザクションをロールバックするのに対して、サービスへの呼び出しが成功すると、その結果がコミットされることをテストしている場合トランザクションの場合、両方のシナリオが正しく動作していることを表明できるように、そのテストの条件を設定するために、その呼び出しの結果を制御する必要があります。

HttpClientは正しい抽象化レベルではないという意見は理解できましたが、消費クラスの「レベル」について質問された人はいないと思います。これは一種の推測です。 サービスとインターフェースへのHTTP呼び出しを抽象化することもできますが、それでも、そのHTTP呼び出しの「ラッパー」が期待どおりに動作していることをテストしたい場合があります。つまり、応答を制御する必要があります。 現在、これはHttpClientに偽のハンドラーを与えることで実現できます。これは私が今行っていることであり、それで問題ありませんが、要点は、そのカバレッジが必要な場合は、ある時点でHttpClientを使用する必要があるということです。少なくとも応答を偽造します。

しかし、今、私にバックペダルをさせてください。 私がそれを見れば見るほど、それはモック可能なインターフェース(言われている)には太すぎます。 「クライアント」という名前に由来する精神的なものかもしれないと思います。 私はそれが間違っていると思うと言っているのではなく、むしろ、最近野生で見られるかもしれない他の「クライアント」実装との整合性が少しずれています。 今日、実際にはサービスファサードである「クライアント」がたくさんあるので、Http CLIENTという名前を見ると、同じことを考えているかもしれません。なぜサービスをモックしないのでしょうか。

ハンドラーがコードにとって重要な部分であることがわかったので、今後はそれを中心に設計します。

基本的に、HttpClientをモックしたいという私たちの願望は、HttpClientの新しいインスタンスを再利用するのではなく、スピンアップするという平均的な開発者の傾向に関連していると思います。これはひどい習慣です。 つまり、HttpClientとは何かを過小評価しており、代わりにハンドラーに焦点を当てる必要があります。 また、ハンドラーは抽象的であるため、簡単にモックすることができます。

はい、売り切れました。 モック用のHttpClientのインターフェイスはもう必要ありません。

長い間苦労した後、私は独自のIHttpHandlerインターフェイスを作成し、ユースケースのためにすべてを実装しました。 私の具体的なコードを読みやすくし、不気味なお尻を使わずにテストをモックアウトすることができます。

今日これに出くわした: http ://www.davesquared.net/2011/04/dont-mock-types-you-dont-own.html

リクエストごとに異なるHTTPヘッダーを送信する必要があるとすぐに、実際のアプリケーションでHttpClient imnのインスタンスを共有することはほぼ不可能です(適切に設計されたRESTful Webサービスと通信する場合に重要です)。 現在、 HttpRequestHeaders DefaultRequestHeadersはインスタンスにバインドされており、インスタンスへの呼び出しではなく、事実上ステートフルになっています。 代わりに{Method}Async()は、HttpClientをステートレスにし、実際に再利用可能にするものを受け入れる必要があります。

@abatishchevただし、各HttpRequestMessageにヘッダーを指定できます。

@richardszalay完全に不可能だとは言いませんが、HttpClientはこの目的のためにうまく設計されていなかったと思います。 {Method}Async()はいずれも$#$ HttpRequestMethod $#$を受け入れず、 SendAsync()のみを受け入れます。 しかし、残りの目的は何ですか?

しかし、残りの目的は何ですか?

ニーズの99%を満たします。

ヘッダーの設定がユースケースの1%であることを意味しますか? 私は疑う。

いずれにせよ、これらのメソッドにHttpResponseMessageを受け入れるオーバーロードがあった場合、これは問題になりません。

@abatishchev疑いはありませんが、いずれにしても、あなたのシナリオに自分自身が見つかった場合は、拡張メソッドを作成します。

私は(モックに対して)偽物を書く必要がなかったので、おそらくHttpMessageHandlerとおそらくHttpRequestMessageをインターフェースするという考えをもてあそびました。 しかし、うさぎの穴をさらに下に行くほど、実際のデータ値オブジェクト(HttpContentなど)を偽造しようとしていることに気づきます。これは無駄な作業です。 したがって、オプションでHttpMessageHandlerをctor引数として受け取るように依存クラスを設計し、単体テストに偽物を使用するのが最も適切なルートだと思います。 HttpClientのラッピングも時間の無駄だと私は主張します...

これにより、実際にインターネットに電話をかけることなく、依存クラスを単体テストできます。これは、必要なことです。 また、偽物は事前に決定されたステータスコードとコンテンツを返すことができるため、依存するコードがそれらを期待どおりに処理していることをテストできます。これも実際に必要なことです。

@dasjestyr HttpMessageHandlerまたはHttpRequestMessageのインターフェイスではなく、 HttpClientのインターフェイスを作成しようとしましたか(ラッパーを作成するようなものです)。

/ me好奇心が強い。

@PureKrome私はそれのためのインターフェースを作成することをスケッチしました、そしてそれは私がそれが無意味であることにすぐに気づいたところです。 HttpClientは、実際には、単体テストのコンテキストでは関係のない一連の内容を抽象化してから、メッセージハンドラーを呼び出します(これは、このスレッドで数回行われたポイントでした)。 また、ラッパーを作成してみましたが、それを実装したり、プラクティスを広めたりするために必要な作業の価値はありませんでした(つまり、「HttpClientを直接使用する代わりに、誰もがこれを行う」)。 必要なものがすべて提供され、文字通り単一のメソッドで構成されているため、ハンドラーに集中する方がはるかに簡単です。

そうは言っても、私独自のRestClientを作成しましたが、それは流動的なリクエストビルダーを提供するという別の問題を解決しましたが、そのクライアントでさえ、単体テストやハンドラーチェーンなどを処理するカスタムハンドラーの実装に使用できるメッセージハンドラーを受け入れます横断的関心事(ロギング、認証、再試行ロジックなど)を解決する方法です。 これは私のRESTクライアントに固有のものではなく、ハンドラーを設定するための優れたユースケースです。 私は実際、この理由からWindows名前空間のHttpClientインターフェイスの方がはるかに好きですが、私は逸脱します。

ハンドラーのインターフェースはまだ役立つと思いますが、そこで停止する必要があります。 次に、モックフレームワークをセットアップして、HttpResponseMessageの事前定義されたインスタンスを返すことができます。

面白い。 私は(個人的な偏見?)私のヘルパーライブラリが(偽の)具体的なメッセージハンドラーを使用するときにうまく機能することを発見しました..対その低レベルのいくつかのインターフェイスのもの。

私はまだそのライブラリを書いたり使用したりする必要はありませんが:)

偽物を作成するための小さなライブラリを作成しても問題はありません。 他に何もすることがないのに退屈しているときにそうするかもしれません。 私のhttpのものはすべてすでに抽象化されてテストされているので、現時点では実際には使用していません。 単体テストの目的でHttpClientをラップしても意味がありません。 ハンドラーを偽造するだけで本当に必要です。 機能の拡張は完全に別のトピックです。

ほとんどのコードベースがモックインターフェイスを使用してテストされる場合、残りのコードベースが同じ方法でテストされると、より便利で一貫性があります。 だから私はインターフェースIHttpClientを見たいと思います。 ADLSDKのIFileSystemOperarionsやADF管理SDKのIDataFactoryManagementClientなど。

私はまだあなたが要点を見逃していると思います。つまり、HttpClientをモックする必要はなく、ハンドラーだけです。 本当の問題は、人々がHttpClientを見る方法です。 インターネットを呼び出すことを考えるときはいつでも新しくする必要があるのは、ランダムなクラスではありません。 実際、アプリケーション全体でクライアントを再利用することをお勧めします。これは非常に大きな問題です。 2つのことを引き離すと、より理にかなっています。

さらに、依存クラスはHttpClientを気にする必要はなく、HttpClientによって返されるデータ(ハンドラーから取得されるデータのみ)を気にする必要があります。 このように考えてください。HttpClientの実装を別のものに置き換える予定はありますか? 可能です...しかし、可能性は低いです。 クライアントの動作方法を変更する必要はありません。なぜわざわざ抽象化するのでしょうか。 メッセージハンドラは変数です。 応答の処理方法を変更したいが、クライアントの処理方法は変更したくない。 WebAPIのパイプラインでさえ、ハンドラーに焦点を合わせています(ハンドラーの委任を参照)。 言うほど、.Netはクライアントを静的にし、管理する必要があると思い始めます...しかし、つまり...何でも。

インターフェイスの目的を覚えておいてください。 それらはテスト用ではありません-それはそれらを活用するための賢い方法でした。 その目的のためだけにインターフェースを作成するのはばかげています。 Microsoftは、メッセージ処理動作を分離するために必要なものを提供し、テストに完全に機能します。 実際、HttpMesageHandlerは抽象的であるため、Moqのようなほとんどのモックフレームワークは引き続き機能すると思います。

Heh @ dasjestyr-私もあなたが私の議論の主要なポイントを逃したかもしれないと思います。

私たち(開発者)がメッセージハンドラーなどについて多くのことを_学ぶ_必要があるという事実は、応答を偽造するために、これらすべてについての私の_主な_ポイントです。 インターフェイスについてはそれほど重要ではありません。 確かに、私は(個人的に)テスト(したがってモック)に関して仮想rラッパーよりもインターフェースを好みます...しかし、それらは実装の詳細です。

この壮大なスレッドの主な要点が、アプリケーションでHttpClientを使用する場合、それをテストするためのPITAであることを強調することであることを願っています。

「HttpClientの配管を学習して、HttpMessageHandlerなどにアクセスする」という現状は悪い状況です。 他の多くのライブラリなどでは、これを行う必要はありません。

だから私はこのPITAを軽減するために何かができることを望んでいました。

はい、PITAは意見です-私はそれを完全に認めます。 まったくPITAだと思わない人もいます。

本当の問題は、人々がHttpClientを見る方法です。 インターネットを呼び出すことを考えるときはいつでも新しくする必要があるのは、ランダムなクラスではありません。

同意しました! しかし、ごく最近まで、これはよく知られていませんでした。これは、ドキュメントや教育などの不足につながる可能性があります。

さらに、依存クラスはHttpClientを気にする必要はなく、HttpClientによって返されるデータだけを気にする必要があります。

うん-同意した。

これはハンドラーから来ます。

何? は? ああ-今、あなたは私にフードを開けて配管について学ぶように頼んでいますか? ...上記を何度も参照してください。

このように考えてください。HttpClientの実装を別のものに置き換える予定はありますか?

いいえ。

..など..

今、私はトロールなどをするつもりはありません。ですから、いつも同じことを何度も繰り返して返信することで、私がジャークになろうとしているとは思わないでください。

私は何年もの間ヘルパーライブラリを使用してきましたが、これはカスタムメッセージハンドラーを使用するための栄光の方法です。 すごい! それはうまく機能し、うまく機能します。 私はこれがもう少し良く露出されるかもしれないと思います...そしてそれは私がこのスレッドが本当に家に帰ることを望んでいるものです。

編集:フォーマット。

(この号のタイトルの文法エラーに気付いたばかりで、今は見えません)

そして、それは私の_本当の_長いゲームでした:)

QED

(実際-とても恥ずかしい😊)

編集:私の一部はそれを編集したくない..懐かしさのために....

(この号のタイトルの文法エラーに気付いたばかりで、今は見えません)

知っている! 同じ!

私たち(開発者)がメッセージハンドラーなどについて多くを学ぶ必要があるという事実は、応答を偽造するために、これらすべてについての私の主なポイントです。 インターフェイスについてはそれほど重要ではありません。 確かに、私は(個人的に)テスト(したがってモック)に関して仮想rラッパーよりもインターフェースを好みます...しかし、それらは実装の詳細です。

え?

HttpMessageHandlerを見たことがありますか? これは、文字通りHttpRequestMessageを受け取り、HttpResponseMessageを返す単一のメソッドである抽象クラスです。これは、単体テストで必要なすべての低レベルのトランスポートジャンクとは別の動作をインターセプトするために作成されたものです。 それを偽造するには、それを実装するだけです。 入ってくるメッセージはあなたがHttpClientに送ったものであり、出てくる応答はあなた次第です。 たとえば、コードがjson本体を正しく処理していることを知りたい場合は、応答に期待するjson本体を返すようにします。 404を正しく処理しているかどうかを確認したい場合は、404を返すようにします。それ以上の基本はありません。 ハンドラーを使用するには、HttpClientのctor引数としてハンドラーを送信するだけです。 ワイヤーを抜いたり、内部の仕組みを学ぶ必要はありません。

この壮大なスレッドの主な要点が、アプリケーションでHttpClientを使用する場合、それをテストするためのPITAであることを強調することであることを願っています。

そして、多くの人が指摘していることの主な要点は、あなたがそれを間違ってやっているということです(それがPITAであり、私はこれを直接ですが丁重に言います)そしてテスト目的でHttpClientがインターフェースされることを要求することは機能を作成することに似ていますこの場合、間違ったレベルで操作しているという根本的な問題に対処する代わりに、バグを補正します。

Windows名前空間のHttpClientは、実際にはハンドラーの概念をフィルターに分離したと思いますが、まったく同じことを行います。 その実装のハンドラー/フィルターは実際にはインターフェースされていると思いますが、これは私が以前に提案したことです。

え?
HttpMessageHandlerを見たことがありますか?

このため、最初はありません。

var payloadData = await _httpClient.GetStringAsync("https://......");

つまり、 HttpClientで公開されているメソッドは本当に素晴らしく、物事を_本当に_簡単にします:)やった! 速くて、シンプルで、うまくいきます、勝ちます!

わかりました-では、テストで使用しましょう...そして、下で何が起こるかを学ぶのに時間を費やす必要があります。これにより、 HttpMessageHandlerなどについて学ぶことができます。

繰り返しますが、これは=> HttpClient (つまり、 HMHなど)の配管に関するこの追加の学習はPITAです。 _その学習を終えたら_ ..はい、それから私はそれを使用する方法などを知っています...私もそれを使い続けるのは好きではありませんが、サードパーティのエンドポイントへのリモート呼び出しをモックするために使う必要があります。 確かに、それは意見です。 私たちは同意しないことに同意することしかできません。 だからお願いします-私はHMHとその使い方を知っています。

そして、多くの人が指摘していることの主な要点は、あなたがそれを間違っているということです

うん-人々は私に同意しません。 問題はありません。 また、人々も私に同意します。 繰り返しますが、問題はありません。

HttpClientをテスト目的でインターフェースすることは、根本的な問題(この場合は間違ったレベルで操作している)に対処するのではなく、バグを補正する機能を作成することに似ています。

わかりました-私はここであなたに敬意を表して同意しません(これは大丈夫です)。 繰り返しますが、異なる意見。

しかし、srsly。 このスレッドは長い間続いています。 私は本当に今までに進んだ。 私は私の作品を言いました、何人かの人々はイエスと言います! ノーと言う人もいました! 何も変わっていません、そして私はただ...現状を受け入れて、すべてで転がりました。

申し訳ありませんが、私の考えを受け入れてくれません。 私は悪い人ではないので、失礼または卑劣に聞こえないようにしてください。 これは、私が成長中にどのように感じたか、そして他の人もどのように感じたかを表現しただけでした。 それで全部です。

私はまったく気分を害していません。 私はもともとクライアントがモック可能であるべきだという同じ意見でこのスレッドに飛び乗ったが、それからHttpClientがであるかについていくつかの本当に良い点が作られたので、私は座ってそれについて本当に考え、そして自分自身でそれに挑戦しようとしましたそして最終的に私はHttpClientはモックするのは間違っているという同じ結論に達しました。そうしようとすると、無駄な運動になり、価値があるよりもはるかに多くの作業が発生します。 それを受け入れることで人生はずっと楽になりました。 だから私も先に進みました。 私は、他の人が最終的に自分自身に休憩を与えて、それが何であるかを理解することを望んでいます。

余談として:

このため、最初はありません。

varpayloadData = await _httpClient.GetStringAsync( "https:// ......");

つまり、HttpClientで公開されているメソッドは非常に優れており、非常に簡単です:)やった! 高速、>シンプル、動作、WIN!

SOLIDに関しては、このインターフェースはとにかく不適切であると私は主張します。 IMOクライアント、要求、応答は3つの異なる責任です。 クライアントの便利な方法は評価できますが、リクエストとレスポンスをクライアントと組み合わせることで緊密な結合を促進しますが、それは個人的な好みです。 同じことを実現するHttpResponseMessageの拡張機能がいくつかありますが、応答メッセージとともに応答を読み取る責任があります。 私の経験では、大規模なプロジェクトでは、「シンプル」は決して「シンプル」ではなく、ほとんどの場合BBOMで終わります。 ただし、これはまったく異なる議論です:)

デザインディスカッション専用の新しいリポジトリができたので、https://github.com/dotnet/designs/issues/9でディスカッションを続けるか、 https: //github.com/dotnet/designsで新しい問題を開いてください。

ありがとう。

たぶん、それはテスト目的のためだけに以下の解決策と考えることができます:

パブリッククラスGeoLocationServiceForTest:GeoLocationService、IGeoLocationService
{{
public GeoLocationServiceForTest(something:something、HttpClient httpClient):base(something)
{{
base._httpClient = httpClient;
}
}

@richardszalayMockHttpを使用することになりました。
ありがとう、リチャード、あなたは私の日を救った!

わかりました、私はインターフェースのないHttpClientで生きることができます。 しかし、 SendAsyncが保護されているために、 HttpMessageHandlerを継承するクラスを実装することを余儀なくされるのは、ひどいことです。

NSubstituteを使用していますが、これは機能しません。

var httpMessageHandler = 
    Substitute.For<HttpMessageHandler>(); // cannot be mocked, not virtual nor interfaced
httpMessageHandler.SendAsync(message, cancellationToken)
    .Return(whatever); // doesn't compile, it's protected
var httpClient = new HttpClient(httpMessageHandler)

本当にがっかり。

このページは役に立ちましたか?
0 / 5 - 0 評価