36

私はついにRESTfulWebインターフェースで動作するいくつかのアプリの作成をいじり始めましたが、一連のテストを実行するためにF5を押すたびにサーバーを叩いているのではないかと心配しています。

基本的に、一連のWeb応答を取得する必要があります。これにより、サーバーに毎回ヒットするのではなく、さまざまな応答を正しく解析していることをテストできます。これを1回実行し、XMLを保存してから、ローカルで作業できると思いました。

ただし、(AFAIK)WebRequest.GetResponseによってのみインスタンス化できるため、WebResponseを「モック」する方法がわかりません

どうやってこの種のことをあざけるのですか?あなたは?私は彼らのサーバーを槌で打っているという事実が本当に好きではありません:SIはコードをあまり変更したくないのですが、これを行うためのエレガントな方法があると思います。

承認後の更新

ウィルの答えは私が必要とした顔の平手打ちでした、私は私が基本的なポイントを逃していることを知っていました!

  • XMLを表すプロキシオブジェクトを返すインターフェイスを作成します。
  • インターフェイスを2回実装します。1つはWebRequestを使用し、もう1つは静的な「応答」を返します。
  • 次に、インターフェースの実装は、応答に基づいて戻りタイプをインスタンス化するか、静的XMLをインスタンス化します。
  • その後、テスト時または本番環境で必要なクラスをサービスレイヤーに渡すことができます。

コードをノックアップしたら、いくつかのサンプルを貼り付けます。

4

6 に答える 6

60

まったく同じことをしようとしているときに、この質問を見つけました。どこにも答えが見つかりませんでしたが、もう少し掘り下げてみると、.NetFrameworkにこれに対するサポートが組み込まれていることがわかりました。

そのプレフィックス(またはURL)を使用するときに呼び出すファクトリオブジェクトを登録できますWebRequest.RegisterPrefixWebRequest.Createファクトリオブジェクトは、を返すIWebRequestCreate単一のメソッドを持つを実装する必要があります。ここであなたはあなたのモックを返すことができます。CreateWebRequestWebRequest

サンプルコードを http://blog.salamandersoft.co.uk/index.php/2009/10/how-to-mock-httpwebrequest-when-unit-testing/に掲載しました。

于 2009-10-18T21:50:01.580 に答える
14

これは、モックを必要としないソリューションです。WebRequest:IWebRequestCreate WebRequestとの 3 つのコンポーネントすべてを実装しますWebResponse。下記参照。私の例では、( をスローすることによって) 失敗したリクエストが生成されWebExceptionますが、「実際の」応答を送信するように適応できるはずです。

class WebRequestFailedCreate : IWebRequestCreate {
    HttpStatusCode status;
    String statusDescription;
    public WebRequestFailedCreate(HttpStatusCode hsc, String sd) {
        status = hsc;
        statusDescription = sd;
    }
    #region IWebRequestCreate Members
    public WebRequest Create(Uri uri) {
        return new WebRequestFailed(uri, status, statusDescription);
    }
    #endregion
}
class WebRequestFailed : WebRequest {
    HttpStatusCode status;
    String statusDescription;
    Uri itemUri;
    public WebRequestFailed(Uri uri, HttpStatusCode status, String statusDescription) {
        this.itemUri = uri;
        this.status = status;
        this.statusDescription = statusDescription;
    }
    WebException GetException() {
        SerializationInfo si = new SerializationInfo(typeof(HttpWebResponse), new System.Runtime.Serialization.FormatterConverter());
        StreamingContext sc = new StreamingContext();
        WebHeaderCollection headers = new WebHeaderCollection();
        si.AddValue("m_HttpResponseHeaders", headers);
        si.AddValue("m_Uri", itemUri);
        si.AddValue("m_Certificate", null);
        si.AddValue("m_Version", HttpVersion.Version11);
        si.AddValue("m_StatusCode", status);
        si.AddValue("m_ContentLength", 0);
        si.AddValue("m_Verb", "GET");
        si.AddValue("m_StatusDescription", statusDescription);
        si.AddValue("m_MediaType", null);
        WebResponseFailed wr = new WebResponseFailed(si, sc);
        Exception inner = new Exception(statusDescription);
        return new WebException("This request failed", inner, WebExceptionStatus.ProtocolError, wr);
    }
    public override WebResponse GetResponse() {
        throw GetException();
    }
    public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state) {
        Task<WebResponse> f = Task<WebResponse>.Factory.StartNew (
            _ =>
            {
                throw GetException();
            },
            state
        );
        if (callback != null) f.ContinueWith((res) => callback(f));
        return f;
    }
    public override WebResponse EndGetResponse(IAsyncResult asyncResult) {
        return ((Task<WebResponse>)asyncResult).Result;
    }

}
class WebResponseFailed : HttpWebResponse {
    public WebResponseFailed(SerializationInfo serializationInfo, StreamingContext streamingContext)
        : base(serializationInfo, streamingContext) {
    }
}

HttpWebResponseサブクラスを作成しないと作成できないため、サブクラスを作成する必要があります。

(メソッド内で) トリッキーな部分は、GetException()オーバーライドできない値を入力することです。たとえばStatusCode、これが私たちの最高の相棒のSerializaionInfo出番です! ここで、オーバーライドできない値を指定します。明らかに、(のHttpWebResponse) 可能な部分をオーバーライドして、残りの部分を取得します。

これらすべてのAddValue()呼び出しで「名前」を取得するにはどうすればよいですか? 例外メッセージから!幸せになるまで、一人一人順番に教えてくれて良かったです。

現在、コンパイラは「廃止」について不平を言うでしょうが、それでも.NET Framework バージョン 4 を含めてこれは機能します。

参照用の(合格)テストケースは次のとおりです。

    [TestMethod, ExpectedException(typeof(WebException))]
    public void WebRequestFailedThrowsWebException() {
        string TestURIProtocol = TestContext.TestName;
        var ResourcesBaseURL = TestURIProtocol + "://resources/";
        var ContainerBaseURL = ResourcesBaseURL + "container" + "/";
        WebRequest.RegisterPrefix(TestURIProtocol, new WebRequestFailedCreate(HttpStatusCode.InternalServerError, "This request failed on purpose."));
        WebRequest wr = WebRequest.Create(ContainerBaseURL);
        try {
            WebResponse wrsp = wr.GetResponse();
            using (wrsp) {
                Assert.Fail("WebRequest.GetResponse() Should not have succeeded.");
            }
        }
        catch (WebException we) {
            Assert.IsInstanceOfType(we.Response, typeof(HttpWebResponse));
            Assert.AreEqual(HttpStatusCode.InternalServerError, (we.Response as HttpWebResponse).StatusCode, "Status Code failed");
            throw we;
        }
    }
于 2012-06-04T14:08:44.277 に答える
2

できません。最善の方法は、プロキシ オブジェクトでラップしてから、それをモックすることです。または、TypeMock のように、モックできない型をインターセプトできるモック フレームワークを使用する必要があります。しかし、あなたはお金について話しているのです。少しラッピングしたほうがいいです。


どうやら少し余分な作業でできるようです。ここで最も投票数の多い回答を確認してください。

于 2008-09-17T20:31:51.550 に答える
2

Microsoft Moles を使用した非常に優れたアプローチを説明している次のブログを以前に見つけました。

http://maraboustork.co.uk/index.php/2011/03/mocking-httpwebresponse-with-moles/

要するに、ソリューションは次のことを示唆しています。

    [TestMethod]
    [HostType("Moles")]
    [Description("Tests that the default scraper returns the correct result")]
    public void Scrape_KnownUrl_ReturnsExpectedValue()
    {
        var mockedWebResponse = new MHttpWebResponse();

        MHttpWebRequest.AllInstances.GetResponse = (x) =>
        {
            return mockedWebResponse;
        };

        mockedWebResponse.StatusCodeGet = () => { return HttpStatusCode.OK; };
        mockedWebResponse.ResponseUriGet = () => { return new Uri("http://www.google.co.uk/someRedirect.aspx"); };
        mockedWebResponse.ContentTypeGet = () => { return "testHttpResponse"; }; 

        var mockedResponse = "<html> \r\n" +
                             "  <head></head> \r\n" +
                             "  <body> \r\n" +
                             "     <h1>Hello World</h1> \r\n" +
                             "  </body> \r\n" +
                             "</html>";

        var s = new MemoryStream();
        var sw = new StreamWriter(s);

            sw.Write(mockedResponse);
            sw.Flush();

            s.Seek(0, SeekOrigin.Begin);

        mockedWebResponse.GetResponseStream = () => s;

        var scraper = new DefaultScraper();
        var retVal = scraper.Scrape("http://www.google.co.uk");

        Assert.AreEqual(mockedResponse, retVal.Content, "Should have returned the test html response");
        Assert.AreEqual("http://www.google.co.uk/someRedirect.aspx", retVal.FinalUrl, "The finalUrl does not correctly represent the redirection that took place.");
    }
于 2011-03-17T12:51:04.733 に答える
0

これは完璧な解決策ではありませんが、以前はうまくいき、シンプルさのために特別な注意が必要でした:

HTTPSシミュレーター

また、typemock フォーラムで文書化された typemock の例:

using System;
using System.IO;
using System.Net;
using NUnit.Framework;
using TypeMock;

namespace MockHttpWebRequest
{
  public class LibraryClass
  {
    public string GetGoogleHomePage()
    {
      HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://www.google.com");
      HttpWebResponse response = (HttpWebResponse)request.GetResponse();
      using (StreamReader reader = new StreamReader(response.GetResponseStream()))
      {
        return reader.ReadToEnd();
      }
    }
  }

  [TestFixture]
  [VerifyMocks]
  public class UnitTests
  {
    private Stream responseStream = null;
    private const string ExpectedResponseContent = "Content from mocked response.";

    [SetUp]
    public void SetUp()
    {
      System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
      byte[] contentAsBytes = encoding.GetBytes(ExpectedResponseContent);
      this.responseStream = new MemoryStream();
      this.responseStream.Write(contentAsBytes, 0, contentAsBytes.Length);
      this.responseStream.Position = 0;
    }

    [TearDown]
    public void TearDown()
    {
      if (responseStream != null)
      {
        responseStream.Dispose();
        responseStream = null;
      }
    }

    [Test(Description = "Mocks a web request using natural mocks.")]
    public void NaturalMocks()
    {
      HttpWebRequest mockRequest = RecorderManager.CreateMockedObject<HttpWebRequest>(Constructor.Mocked);
      HttpWebResponse mockResponse = RecorderManager.CreateMockedObject<HttpWebResponse>(Constructor.Mocked);
      using (RecordExpectations recorder = RecorderManager.StartRecording())
      {
        WebRequest.Create("http://www.google.com");
        recorder.CheckArguments();
        recorder.Return(mockRequest);

        mockRequest.GetResponse();
        recorder.Return(mockResponse);

        mockResponse.GetResponseStream();
        recorder.Return(this.responseStream);
      }

      LibraryClass testObject = new LibraryClass();
      string result = testObject.GetGoogleHomePage();
      Assert.AreEqual(ExpectedResponseContent, result);
    }

    [Test(Description = "Mocks a web request using reflective mocks.")]
    public void ReflectiveMocks()
    {
      Mock<HttpWebRequest> mockRequest = MockManager.Mock<HttpWebRequest>(Constructor.Mocked);
      MockObject<HttpWebResponse> mockResponse = MockManager.MockObject<HttpWebResponse>(Constructor.Mocked);
      mockResponse.ExpectAndReturn("GetResponseStream", this.responseStream);
      mockRequest.ExpectAndReturn("GetResponse", mockResponse.Object);

      LibraryClass testObject = new LibraryClass();
      string result = testObject.GetGoogleHomePage();
      Assert.AreEqual(ExpectedResponseContent, result);
    }
  }
}
于 2009-04-01T22:33:23.497 に答える
0

NSubstitute を使用できます。

        var httpWebResponse = Substitute.For<HttpWebResponse>();
        httpWebResponse.StatusCode.Returns(HttpStatusCode.NotFound);
        httpWebResponse.StatusDescription.Returns("Not Found");
于 2022-02-23T10:49:45.287 に答える