Можно ли издеваться a.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 ?.

любые предложения с некоторым примером кода были бы наиболее оценены!

7   58  

7 ответов:

вы можете изменить свой потребляющий код, чтобы принять интерфейс для фабрики, которая создает запросы и ответы, которые могут быть издевались, которые обертывают фактическую реализацию.

Update: Revisiting

я получал downvotes долго после того, как мой ответ был принят, и я признаю, что мой первоначальный ответ был низкого качества и сделал большое предположение.

издевательство над HttpWebRequest в 4.5+

растерянность от моего первоначального ответа заключается в том, что вы можете издеваться HttpWebResponse в 4.5, но не более ранние версии. Издеваясь над ним в 4.5 также использует устаревшие конструкторы. Таким образом, рекомендуемый курс действий-абстрагировать запрос и ответ. В любом случае, ниже приведен полный рабочий тест с использованием .NET 4.5 с Moq 4.2.

[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 at минимум):

[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();
    }
}

вместо того, чтобы издеваться над HttpWebResponse, я бы обернул вызов за интерфейсом и издевался над этим интерфейсом.

Если вы тестируете, попадает ли веб-ответ на сайт, который я тоже хочу, это другой тест, чем если бы класс A вызывал интерфейс WebResponse для получения необходимых данных.

для издевательства над интерфейсом я предпочитаю носорог издевается. Смотрите здесь о том, как использовать его.

Если это поможет, пожалуйста, найдите ниже код, проиллюстрированный в принятом ответе, используя NSubstitute вместо Moq

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);
}

модульный тест выполняется и успешно проходит.

до голосования, данного на ответ, который я искал в течение некоторого времени, как это сделать эффективно.

ни один из HTTP-стека Microsoft не был разработан с модульным тестированием и разделением в виду.

у вас есть три варианта:

  • сделать вызов в интернете как можно меньше (т. е. отправить и получить обратно данные и перейти к другим методам) и проверить остальные. Что касается веб-вызова, там должно быть много магии, происходящей там и очень простой.
  • оберните HTTP-вызов в другой класс и передайте свой макет объекта, пока тестирование.
  • обертывание HttpWebResponse и HttpWebRequest по двум другим классам. Это то, что команда MVC сделала с HttpContext.

второй вариант:

interface IWebCaller
{
    string CallWeb(string address);
}

вы действительно можете вернуть HttpWebResponse без насмешки смотрите мой ответ здесь. Он не требует каких-либо" внешних "интерфейсов проксирования, только" стандартный"WebRequestWebResponse и ICreateWebRequest.

Если вам не нужен доступ к HttpWebResponse и просто WebResponse это еще проще; мы делаем это в наших модульных тестах, чтобы вернуть "готовые" ответы на контент для потребления. Мне пришлось "пройти лишнюю милю", чтобы вернуть фактические коды состояния HTTP, например, для имитации 404 ответов, которые требуют использования HttpWebResponse Так что вы можете получить доступ к StatusCode property et al.

другие решения, предполагающие, что все HttpWebXXXигнорирует все, что поддерживается WebRequest.Create() кроме HTTP, который может быть обработчиком для любого зарегистрированного префикса, который вы хотите использовать (через WebRequest.RegisterPrefix() и если вы игнорируете это, вы упускаете, потому что это отличный способ выставить другие потоки контента, к которым у вас в противном случае нет доступа, например, Embeeded Resource Streams, File потоков и т. д.

кроме того, явное приведение возврата WebRequest.Create() до HttpWebRequest и путь к обрыву, так как метод возвращает тип WebRequest и снова, показывает некоторое незнание того, как этот API на самом деле работает.

Я нашел отличное решение в этой блоге:

его очень проста в использовании, вам просто нужно сделать это:

string response = "my response string here";
WebRequest.RegisterPrefix("test", new TestWebRequestCreate());
TestWebRequest request = TestWebRequestCreate.CreateTestRequest(response);

и скопируйте эти файлы в свой проект:

    class TestWebRequestCreate : IWebRequestCreate
{
    static WebRequest nextRequest;
    static object lockObject = new object();

    static public WebRequest NextRequest
    {
        get { return nextRequest ;}
        set
        {
            lock (lockObject)
            {
                nextRequest = value;
            }
        }
    }

    /// <summary>See <see cref="IWebRequestCreate.Create"/>.</summary>
    public WebRequest Create(Uri uri)
    {
        return nextRequest;
    }

    /// <summary>Utility method for creating a TestWebRequest and setting
    /// it to be the next WebRequest to use.</summary>
    /// <param name="response">The response the TestWebRequest will return.</param>
    public static TestWebRequest CreateTestRequest(string response)
    {
        TestWebRequest request = new TestWebRequest(response);
        NextRequest = request;
        return request;
    }
}

class TestWebRequest : WebRequest
{
    MemoryStream requestStream = new MemoryStream();
    MemoryStream responseStream;

    public override string Method { get; set; }
    public override string ContentType { get; set; }
    public override long ContentLength { get; set; }

    /// <summary>Initializes a new instance of <see cref="TestWebRequest"/>
    /// with the response to return.</summary>
    public TestWebRequest(string response)
    {
        responseStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(response));
    }

    /// <summary>Returns the request contents as a string.</summary>
    public string ContentAsString()
    {
        return System.Text.Encoding.UTF8.GetString(requestStream.ToArray());
    }

    /// <summary>See <see cref="WebRequest.GetRequestStream"/>.</summary>
    public override Stream GetRequestStream()
    {
        return requestStream;
    }

    /// <summary>See <see cref="WebRequest.GetResponse"/>.</summary>
    public override WebResponse GetResponse()
    {
        return new TestWebReponse(responseStream);
    }
}

class TestWebReponse : WebResponse
{
    Stream responseStream;

    /// <summary>Initializes a new instance of <see cref="TestWebReponse"/>
    /// with the response stream to return.</summary>
    public TestWebReponse(Stream responseStream)
    {
        this.responseStream = responseStream;
    }

    /// <summary>See <see cref="WebResponse.GetResponseStream"/>.</summary>
    public override Stream GetResponseStream()
    {
        return responseStream;
    }
}