ich habe einen Integrationstest, der einige JSON-Ergebnisse von einem Drittanbieter-Server abruft. Es ist wirklich einfach und funktioniert großartig.
Ich hatte gehofft, nicht mehr auf diesen Server zuzugreifen und Moq
(oder eine beliebige Mocking-Bibliothek wie ninject usw.) zu verwenden, um das Ergebnis zurückzuerzwingen.
ist das möglich?
Hier ist ein Beispielcode: -
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..
}
und natürlich heißt diese methode aus meinem test.
Ich dachte, dass ich in diese Methode (oder eine Eigenschaft der Klasse?) Vielleicht ein verspottetes httpWebResponse
oder etwas anderes übergehen muss, war mir aber nicht sicher, ob dies der Weg war. Außerdem ist die Antwort eine Ausgabe einer httpWebRequest.GetResponse()
-Methode. Vielleicht muss ich nur ein verspottetes HttpWebRequest
übergeben.
vorschläge mit einigem Beispielcode werden am meisten geschätzt!
Möglicherweise möchten Sie Ihren konsumierenden Code ändern, um eine Schnittstelle für eine Factory aufzunehmen, die Anforderungen und Antworten erstellt, die nachgestellt werden können und die die eigentliche Implementierung einschließen.
Ich habe lange nach der Annahme meiner Antwort Ablehnungen erhalten, und ich gebe zu, dass meine ursprüngliche Antwort von schlechter Qualität war und eine große Annahme gemacht hat.
Die Verwirrung von meiner ursprünglichen Antwort liegt in der Tatsache, dass Sie HttpWebResponse
in 4.5 verspotten können, aber nicht in früheren Versionen. Beim Verspotten in 4.5 werden auch veraltete Konstruktoren verwendet. Die empfohlene Vorgehensweise besteht darin, die Anforderung und die Antwort zu abstrahieren. Im Folgenden finden Sie einen vollständigen Funktionstest unter Verwendung von .NET 4.5 mit 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);
}
Hier ist eine absolut sichere Implementierung einer Abstraktion, die für frühere Versionen (mindestens bis 3.5) funktioniert:
[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();
}
}
Anstatt die HttpWebResponse zu verspotten, würde ich den Aufruf hinter einer Schnittstelle einschließen und diese Schnittstelle verspotten.
Wenn Sie testen, trifft die Webantwort auf die Site zu, die ich auch haben möchte. Dies ist ein anderer Test als wenn Klasse A die WebResponse-Schnittstelle aufruft, um die erforderlichen Daten abzurufen.
Zum Verspotten eines Interfaces bevorzuge ich Rhino-Verspottungen . Siehe hier zur Verwendung.
Wenn es hilft, finden Sie unten den Code, der in der akzeptierten Antwort mit NSubstitute anstelle von Moq dargestellt ist
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 läuft und besteht erfolgreich.
Nach oben Abstimmung über die Antwort Ich habe schon seit einiger Zeit nach einer effektiven Vorgehensweise gesucht.
Keiner der HTTP-Stacks von Microsoft wurde im Hinblick auf Komponententests und -trennung entwickelt.
Sie haben drei Möglichkeiten:
HttpWebResponse
und HttpWebRequest
in zwei weitere Klassen ein. Dies hat das MVC-Team mit HttpContext
getan.Zweite Option:
interface IWebCaller
{
string CallWeb(string address);
}
Sie können tatsächlich HttpWebResponse
ohne Spott zurückgeben, siehe meine Antwort hier . Es werden keine "externen" Proxy-Schnittstellen benötigt, nur die "Standard" -Positionen WebRequest
WebResponse
und ICreateWebRequest
.
Wenn Sie keinen Zugriff auf HttpWebResponse
benötigen und nur mit WebResponse
umgehen können, ist dies noch einfacher. Wir tun dies in unseren Unit-Tests, um "vorgefertigte" Inhaltsantworten für den Verbrauch zurückzugeben. Ich musste "die Extrameile gehen", um tatsächliche HTTP-Statuscodes zurückzugeben, um z.B. 404 Antworten, für die Sie HttpWebResponse
verwenden müssen, damit Sie ua auf die Eigenschaft StatusCode
zugreifen können.
Die anderen Lösungen, die davon ausgehen, dass alles HttpWebXXX
ist, ignorieren alles, was von WebRequest.Create()
unterstützt wird, mit Ausnahme von HTTP , das ein Handler für sein kann Jedes registrierte Präfix, das Sie verwenden möchten (über WebRequest.RegisterPrefix()
und wenn Sie dies ignorieren, verpassen Sie es, da es eine großartige Möglichkeit ist, andere Inhaltsströme anzuzeigen, auf die Sie sonst keinen Zugriff haben, z. B. Embeeded Resource Streams, Dateistreams usw.
Das explizite Umwandeln der Rückgabe von WebRequest.Create()
in HttpWebRequest
ist ein Pfad zum Unterbrechen , da die Methode den Rückgabetyp zurückgibt ist WebRequest
und zeigt wiederum einige Unkenntnisse darüber, wie diese API tatsächlich funktioniert.