Подтвердить что ты не робот

Можно ли издеваться над .NET HttpWebResponse?

У меня есть тест интеграции, который захватывает некоторый результат json от стороннего сервера. Это действительно просто и отлично работает.

Я надеялся прекратить на самом деле поражать этот сервер и использовать Moq (или любую библиотеку Mocking, такую ​​как 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?.

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

4b9b3361

Ответ 1

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

Обновление: пересмотр

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

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

Лучший ответ: аннотация ответа и запроса

Здесь более безопасная реализация barebone-абстракции, которая будет работать для предыдущих версий (ну, по крайней мере, до 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();
    }
}

Ответ 3

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

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

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

Ответ 4

Если это поможет, пожалуйста, найдите ниже код, показанный в принятом ответе, используя 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);
}

Unit Test выполняется успешно.

На голосование, приведенное в ответе, я искал какое-то время, как это сделать эффективно.

Ответ 5

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

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

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

Вторая опция:

interface IWebCaller
{
    string CallWeb(string address);
}

Ответ 6

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

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

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

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

Ответ 7

Я нашел отличное решение в этом сообщении :

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

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