web-dev-qa-db-ja.com

.NET HttpWebResponseをモックアウトすることは可能ですか?

サードパーティのサーバーからjsonの結果を取得する統合テストがあります。それは本当にシンプルで素晴らしい作品です。

このサーバーへの実際のヒットを停止し、Moq(またはninjectなどのモッキングライブラリ)を使用してハイジャックし、結果を強制的に返すことを望んでいました。

これは可能ですか?

以下にサンプルコードを示します。

_public Foo GoGetSomeJsonForMePleaseKThxBai()
{
    // prep stuff ...

    // Now get json please.
    HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create("Http://some.fancypants.site/api/hiThere);
    httpWebRequest.Method = WebRequestMethods.Http.Get;

    string responseText;

    using (var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
    {
        using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
        {
            json = streamReader.ReadToEnd().ToLowerInvariant();
        }
    }

    // Check the value of the json... etc..
}
_

そしてもちろん、このメソッドは私のテストから呼び出されます。

このメソッド(またはクラスのプロパティ)にhttpWebResponseなどのモックを渡す必要があるかもしれないと考えていましたが、これが方法であるかどうかはあまりわかりませんでした。また、応答はhttpWebRequest.GetResponse()メソッドからの出力です。したがって、たぶん、模擬のHttpWebRequest?を渡す必要があるだけです。

いくつかのサンプルコードを使用した提案は、最も評価されるでしょう。

62
Pure.Krome

実際の実装をラップする、モック可能な要求と応答を作成するファクトリーのインターフェースを取り込むように、消費するコードを変更したい場合があります。

更新:再訪

私は答えが受け入れられてからずっとずっと下票を得ており、元の答えは質が悪く、大きな仮定をしたことを認めています。

4.5+でのHttpWebRequestのモック

私の元の答えからの混乱は、4.5でHttpWebResponseをモックできるが、以前のバージョンではモックできないという事実にあります。 4.5でのモックは、廃止されたコンストラクタも利用します。したがって、推奨される一連のアクションは、要求と応答を抽象化することです。とにかく、以下はMoq 4.2で.NET 4.5を使用した完全な動作テストです。

[Test]
public void Create_should_create_request_and_respond_with_stream()
{
    // arrange
    var expected = "response content";
    var expectedBytes = Encoding.UTF8.GetBytes(expected);
    var responseStream = new MemoryStream();
    responseStream.Write(expectedBytes, 0, expectedBytes.Length);
    responseStream.Seek(0, SeekOrigin.Begin);

    var response = new Mock<HttpWebResponse>();
    response.Setup(c => c.GetResponseStream()).Returns(responseStream);

    var request = new Mock<HttpWebRequest>();
    request.Setup(c => c.GetResponse()).Returns(response.Object);

    var factory = new Mock<IHttpWebRequestFactory>();
    factory.Setup(c => c.Create(It.IsAny<string>()))
        .Returns(request.Object);

    // act
    var actualRequest = factory.Object.Create("http://www.google.com");
    actualRequest.Method = WebRequestMethods.Http.Get;

    string actual;

    using (var httpWebResponse = (HttpWebResponse)actualRequest.GetResponse())
    {
        using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
        {
            actual = streamReader.ReadToEnd();
        }
    }


    // assert
    actual.Should().Be(expected);
}

public interface IHttpWebRequestFactory
{
    HttpWebRequest Create(string uri);
}

より良い答え:応答と要求を抽象化する

以前のバージョン(少なくとも3.5まで)で機能する抽象化のより安全な最低限の実装を次に示します。

[Test]
public void Create_should_create_request_and_respond_with_stream()
{
    // arrange
    var expected = "response content";
    var expectedBytes = Encoding.UTF8.GetBytes(expected);
    var responseStream = new MemoryStream();
    responseStream.Write(expectedBytes, 0, expectedBytes.Length);
    responseStream.Seek(0, SeekOrigin.Begin);

    var response = new Mock<IHttpWebResponse>();
    response.Setup(c => c.GetResponseStream()).Returns(responseStream);

    var request = new Mock<IHttpWebRequest>();
    request.Setup(c => c.GetResponse()).Returns(response.Object);

    var factory = new Mock<IHttpWebRequestFactory>();
    factory.Setup(c => c.Create(It.IsAny<string>()))
        .Returns(request.Object);

    // act
    var actualRequest = factory.Object.Create("http://www.google.com");
    actualRequest.Method = WebRequestMethods.Http.Get;

    string actual;

    using (var httpWebResponse = actualRequest.GetResponse())
    {
        using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
        {
            actual = streamReader.ReadToEnd();
        }
    }


    // assert
    actual.Should().Be(expected);
}

public interface IHttpWebRequest
{
    // expose the members you need
    string Method { get; set; }

    IHttpWebResponse GetResponse();
}

public interface IHttpWebResponse : IDisposable
{
    // expose the members you need
    Stream GetResponseStream();
}

public interface IHttpWebRequestFactory
{
    IHttpWebRequest Create(string uri);
}

// barebones implementation

private class HttpWebRequestFactory : IHttpWebRequestFactory
{
    public IHttpWebRequest Create(string uri)
    {
        return new WrapHttpWebRequest((HttpWebRequest)WebRequest.Create(uri));
    }
}

public class WrapHttpWebRequest : IHttpWebRequest
{
    private readonly HttpWebRequest _request;

    public WrapHttpWebRequest(HttpWebRequest request)
    {
        _request = request;
    }

    public string Method
    {
        get { return _request.Method; }
        set { _request.Method = value; }
    }

    public IHttpWebResponse GetResponse()
    {
        return new WrapHttpWebResponse((HttpWebResponse)_request.GetResponse());
    }
}

public class WrapHttpWebResponse : IHttpWebResponse
{
    private WebResponse _response;

    public WrapHttpWebResponse(HttpWebResponse response)
    {
        _response = response;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_response != null)
            {
                ((IDisposable)_response).Dispose();
                _response = null;
            }
        }
    }

    public Stream GetResponseStream()
    {
        return _response.GetResponseStream();
    }
}
67
HackedByChinese
8

HttpWebResponseをモックアウトするのではなく、呼び出しをインターフェイスの背後にラップし、そのインターフェイスをモックします。

あなたがテストしている場合、Webレスポンスが私が望むサイトにヒットするかどうかは、クラスAがWebResponseインターフェースを呼び出して必要なデータを取得する場合とは異なるテストです。

インターフェイスをモックするには、 Rhino mocks を好みます。使い方については こちら をご覧ください。

5
David Basarab

役立つ場合は、Moqの代わりにNSubstituteを使用して、受け入れられた回答に示されているコードを見つけてください。

using NSubstitute; /*+ other assemblies*/

[TestMethod]
public void Create_should_create_request_and_respond_with_stream()
{
   //Arrange
   var expected = "response content";
   var expectedBytes = Encoding.UTF8.GetBytes(expected);
   var responseStream = new MemoryStream();
   responseStream.Write(expectedBytes, 0, expectedBytes.Length);
   responseStream.Seek(0, SeekOrigin.Begin);

   var response = Substitute.For<HttpWebResponse>();
   response.GetResponseStream().Returns(responseStream);

   var request = Substitute.For<HttpWebRequest>();
   request.GetResponse().Returns(response);

   var factory = Substitute.For<IHttpWebRequestFactory>();
   factory.Create(Arg.Any<string>()).Returns(request);

   //Act
   var actualRequest = factory.Create("http://www.google.com");
   actualRequest.Method = WebRequestMethods.Http.Get;

   string actual;

   using (var httpWebResponse = (HttpWebResponse)actualRequest.GetResponse())
   {
       using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
       {
           actual = streamReader.ReadToEnd();
       }
   }

   //Assert
   Assert.AreEqual(expected, actual);
}

public interface IHttpWebRequestFactory
{
    HttpWebRequest Create(string uri);
}

単体テストが実行され、正常に合格します。

これを効果的に行う方法を私はしばらく探していた答えに与えられた賛成票。

3
David Hall

マイクロソフトのHTTPスタックは、単体テストと分離を念頭に置いて開発されたものではありません。

次の3つのオプションがあります。

  • Webへの呼び出しをできる限り小さくして(つまり、データを送受信して他のメソッドに渡す)、残りをテストします。 Webコールに関する限り、そこには多くの魔法があり、非常に簡単です。
  • HTTP呼び出しを別のクラスにラップし、テスト中にモックオブジェクトを渡します。
  • HttpWebResponseHttpWebRequestを他の2つのクラスでラップします。これは、MVCチームがHttpContextで行ったことです。

2番目のオプション:

interface IWebCaller
{
    string CallWeb(string address);
}
2
Aliostad

実際には、モックせずにHttpWebResponseを返すことができます。私の答え here を参照してください。 「外部」プロキシインターフェイスは不要で、「標準」WebRequestWebResponseおよびICreateWebRequestのみが必要です。

HttpWebResponseにアクセスする必要がなく、WebResponseだけを処理できる場合はさらに簡単です。ユニットテストでこれを行い、「プレハブ」コンテンツレスポンスを消費用に返します。実際のHTTPステータスコードを返すために、「余分な距離を行く」必要がありました。 404応答ではHttpWebResponseを使用する必要があるため、StatusCodeプロパティなどにアクセスできます。

すべてがHttpWebXXXであると仮定する他のソリューションは、HTTPを除くWebRequest.Create()でサポートされるすべてを無視します。使用したい登録済みプレフィックス(WebRequest.RegisterPrefix()を使用し、それを無視すると、他のコンテンツストリームを公開する優れた方法であるため、見逃してしまいます。たとえば、Embeded Resourceストリーム、ファイルストリームなど.

また、WebRequest.Create()の戻り値をHttpWebRequestに明示的にキャストすると、breakageへのパスになります。はWebRequestであり、再び、そのAPIが実際にどのように機能するかについての無知を示​​しています。

1
escape-llc