Можно ли макет .NET HttpWebResponse? - PullRequest
61 голосов
/ 22 марта 2012

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

Я надеялся прекратить фактически поражать этот сервер и использовать Moq (или любую библиотеку Mocking, например, ninject и т. Д.) Для взлома и принудительного возврата результата.1005 * возможно ли это?

Вот пример кода: -

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 ]

66 голосов
/ 22 марта 2012

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

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

IЯ получал отрицательные отзывы задолго до того, как мой ответ был принят, и я признаю, что мой исходный ответ был низкого качества и сделал большое предположение.заключается в том, что вы можете издеваться над 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 как минимум):

[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();
    }
}
8 голосов
/ 22 марта 2012
5 голосов
/ 22 марта 2012

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

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

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

3 голосов
/ 07 июля 2015

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

Юнит тестовый прогон и успешно пройден.

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

2 голосов
/ 22 марта 2012

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

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

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

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

interface IWebCaller
{
    string CallWeb(string address);
}
1 голос
/ 04 июня 2012

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

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

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

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

0 голосов
/ 07 апреля 2015

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

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

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