j'ai un test d'intégration qui saisit le résultat json d'un serveur tiers. C'est vraiment simple et fonctionne très bien.
J'espérais arrêter de frapper réellement ce serveur et d'utiliser Moq
(ou n'importe quelle bibliothèque Mocking, comme ninject, etc.) pour détourner et forcer le résultat de retour.
est-ce possible?
Voici un exemple de code: -
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..
}
et bien sûr, cette méthode est appelée à partir de mon test.
Je pensais que je devais peut-être passer dans cette méthode (ou une propriété de la classe?) Un httpWebResponse
moqué ou quelque chose mais je ne savais pas trop si c'était le cas. De plus, la réponse est une sortie d'une méthode httpWebRequest.GetResponse()
.. alors peut-être ai-je juste besoin de passer un HttpWebRequest
?.
toutes les suggestions avec un exemple de code seraient les plus appréciées!
Vous souhaiterez peut-être modifier votre code consommateur pour intégrer une interface pour une fabrique qui crée des demandes et des réponses qui peuvent être moquées et qui enveloppent l'implémentation réelle.
J'ai reçu des votes négatifs longtemps après que ma réponse a été acceptée, et j'admets que ma réponse d'origine était de mauvaise qualité et a fait une grande hypothèse.
La confusion de ma réponse d'origine réside dans le fait que vous pouvez vous moquer de HttpWebResponse
dans 4.5, mais pas dans les versions antérieures. Se moquer de lui en 4.5 utilise également des constructeurs obsolètes. Ainsi, la ligne de conduite recommandée consiste à résumer la demande et la réponse. Quoi qu'il en soit, ci-dessous est un test de travail complet utilisant .NET 4.5 avec 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);
}
Voici une implémentation bare-bones plus sûre d'une abstraction qui fonctionnera pour les versions précédentes (enfin, jusqu'à 3,5 au moins):
[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();
}
}
j'ai écrit un ensemble d'interfaces et d'adaptateurs précisément à cet effet il y a quelques années.
Plutôt que de se moquer de HttpWebResponse, je préfère envelopper l'appel derrière une interface et me moquer de cette interface.
Si vous testez la réponse Web arrive-t-elle sur le site, je le veux aussi, c'est un test différent de si la classe A appelle l'interface WebResponse pour obtenir les données nécessaires.
Pour se moquer d'une interface, je préfère Rhino se moque . Voir ici sur la façon de l'utiliser.
Si cela peut aider, veuillez trouver ci-dessous le code illustré dans la réponse acceptée en utilisant NSubstitute à la place de 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);
}
Le test unitaire s'est exécuté et a réussi.
Vote positif sur la réponse Je cherche depuis un certain temps comment faire cela efficacement.
Aucune des piles HTTP de Microsoft n'a été développée en gardant à l'esprit les tests unitaires et la séparation.
Vous avez trois options:
HttpWebResponse
et HttpWebRequest
par deux autres classes. C'est ce que l'équipe MVC a fait avec HttpContext
.Deuxième option:
interface IWebCaller
{
string CallWeb(string address);
}
Vous pouvez réellement retourner HttpWebResponse
sans vous moquer, voir ma réponse ici . Il ne nécessite aucune interface de proxy "externe", uniquement les interfaces "standard" WebRequest
WebResponse
et ICreateWebRequest
.
Si vous n'avez pas besoin d'accéder à HttpWebResponse
et ne pouvez gérer que WebResponse
, c'est encore plus facile; nous le faisons dans nos tests unitaires pour renvoyer des réponses de contenu "préfabriquées" à la consommation. J'ai dû "faire un effort supplémentaire" pour renvoyer les codes d'état HTTP réels, pour simuler par exemple 404 réponses qui nécessitent que vous utilisiez HttpWebResponse
pour pouvoir accéder à la propriété StatusCode
et al.
Les autres solutions supposant que tout est HttpWebXXX
ignorent tout ce qui est pris en charge par WebRequest.Create()
sauf HTTP , qui peut être un gestionnaire pour tout préfixe enregistré que vous souhaitez utiliser (via WebRequest.RegisterPrefix()
et si vous l'ignorez, vous le manquez, car c'est un excellent moyen d'exposer d'autres flux de contenu auxquels vous n'avez autrement aucun moyen d'accéder, par exemple Embeeded Resource Flux, flux de fichiers, etc.
En outre, le transtypage explicite du retour de WebRequest.Create()
en HttpWebRequest
est un chemin vers la rupture , puisque la méthode retourne le type est WebRequest
et encore une fois, montre une certaine ignorance du fonctionnement réel de cette API.