Tengo algunos problemas al intentar ajustar mi código para usarlo en pruebas unitarias. El problema es este. Tengo la interfaz IHttpHandler:
public interface IHttpHandler
{
HttpClient client { get; }
}
Y la clase que lo usa, HttpHandler:
public class HttpHandler : IHttpHandler
{
public HttpClient client
{
get
{
return new HttpClient();
}
}
}
Y luego la clase Connection, que usa simpleIOC para inyectar la implementación del cliente:
public class Connection
{
private IHttpHandler _httpClient;
public Connection(IHttpHandler httpClient)
{
_httpClient = httpClient;
}
}
Y luego tengo un proyecto de prueba unitaria que tiene esta clase:
private IHttpHandler _httpClient;
[TestMethod]
public void TestMockConnection()
{
var client = new Connection(_httpClient);
client.doSomething();
// Here I want to somehow create a mock instance of the http client
// Instead of the real one. How Should I approach this?
}
Ahora, obviamente, tendré métodos en la clase Connection que recuperarán datos (JSON) de mi back-end. Sin embargo, quiero escribir pruebas unitarias para esta clase y, obviamente, no quiero escribir pruebas contra el back-end real, sino una burla. He intentado buscar en Google una buena respuesta a esto sin gran éxito. Puedo y he usado Moq para simular antes, pero nunca en algo como httpClient. ¿Cómo debo abordar este problema?
Gracias por adelantado.
fuente

HttpClienten su interfaz es donde está el problema. Estás obligando a tu cliente a utilizar laHttpClientclase concreta. En su lugar, debe exponer una abstracción deHttpClient.Respuestas:
Su interfaz expone la
HttpClientclase concreta , por lo tanto, cualquier clase que use esta interfaz está vinculada a ella, esto significa que no se puede burlar de ella.HttpClientno hereda de ninguna interfaz por lo que tendrás que escribir la tuya propia. Sugiero un patrón similar al de un decorador :Y tu clase se verá así:
El punto en todo esto es que
HttpClientHandlercrea el suyo propioHttpClient, entonces, por supuesto, podría crear múltiples clases que se implementanIHttpHandlerde diferentes maneras.El problema principal con este enfoque es que está escribiendo efectivamente una clase que solo llama a métodos en otra clase, sin embargo, podría crear una clase que herede de
HttpClient(Ver ejemplo de Nkosi , es un mejor enfoque mucho más que la mía). La vida sería mucho más fácil siHttpClienttuviera una interfaz de la que pudiera burlarse, desafortunadamente no es así.Sin embargo, este ejemplo no es el billete dorado.
IHttpHandlertodavía confía enHttpResponseMessage, que pertenece alSystem.Net.Httpespacio de nombres, por lo tanto, si necesita otras implementaciones además deHttpClient, tendrá que realizar algún tipo de mapeo para convertir sus respuestas enHttpResponseMessageobjetos. Por supuesto, esto es sólo un problema si es necesario utilizar múltiples implementaciones deIHttpHandler, pero no se ve como lo hace así que no es el fin del mundo, pero es algo en que pensar.De todos modos, simplemente puede burlarse
IHttpHandlersin tener que preocuparse por laHttpClientclase concreta , ya que se ha abstraído.Recomiendo probar los métodos no asíncronos , ya que todavía llaman a los métodos asíncronos pero sin la molestia de tener que preocuparse por los métodos asíncronos de prueba unitaria, consulte aquí
fuente
La extensibilidad de HttpClient radica en el
HttpMessageHandlerpaso al constructor. Su intención es permitir implementaciones específicas de la plataforma, pero también puede simularlo. No es necesario crear un contenedor de decorador para HttpClient.Si prefiere un DSL a usar Moq, tengo una biblioteca en GitHub / Nuget que hace las cosas un poco más fáciles: https://github.com/richardszalay/mockhttp
fuente
var client = new HttpClient()convar client = ClientFactory()y configurar un campointernal static Func<HttpClient> ClientFactory = () => new HttpClient();y en el nivel de prueba puede volver a escribir este campo.HttpClientFactorycomo enFunc<HttpClient>. Dado que veo HttpClient como puramente un detalle de implementación y no una dependencia, usaré estática tal como ilustré anteriormente. Estoy completamente de acuerdo con las pruebas que manipulan los componentes internos. Si me importa el puro-ismo, pondré servidores completos y probaré rutas de código en vivo. Usar cualquier tipo de simulacro significa que acepta una aproximación del comportamiento, no el comportamiento real.Estoy de acuerdo con algunas de las otras respuestas en que el mejor enfoque es simular HttpMessageHandler en lugar de envolver HttpClient. Esta respuesta es única en el sentido de que todavía inyecta HttpClient, lo que le permite ser un singleton o administrado con inyección de dependencia.
"HttpClient está diseñado para ser instanciado una vez y reutilizado durante la vida de una aplicación". ( Fuente ).
Mocking HttpMessageHandler puede ser un poco complicado porque SendAsync está protegido. Aquí hay un ejemplo completo, usando xunit y Moq.
fuente
mockMessageHandler.Protected()fue el asesino. Gracias por este ejemplo. Permite escribir la prueba sin modificar la fuente en absoluto..ReturnsAsync(new HttpResponseMessage {StatusCode = HttpStatusCode.OK, Content = new StringContent(testContent)})Esta es una pregunta común, y yo estaba muy del lado de querer la capacidad de burlarse de HttpClient, pero creo que finalmente me di cuenta de que no debería burlarse de HttpClient. Parece lógico hacerlo, pero creo que las cosas que vemos en las bibliotecas de código abierto nos han lavado el cerebro.
A menudo vemos "Clientes" por ahí que simulamos en nuestro código para que podamos probar de forma aislada, por lo que automáticamente intentamos aplicar el mismo principio a HttpClient. HttpClient realmente hace mucho; puede pensar en él como un administrador para HttpMessageHandler, por lo que no quiere burlarse de eso, y es por eso que todavía no tiene una interfaz. La parte que realmente le interesa para las pruebas unitarias, o incluso para diseñar sus servicios, es HttpMessageHandler, ya que eso es lo que devuelve la respuesta, y puede burlarse de eso.
También vale la pena señalar que probablemente debería comenzar a tratar a HttpClient como algo más importante. Por ejemplo: Mantenga la instalación de nuevos HttpClients al mínimo. Reutilícelos, están diseñados para ser reutilizados y use una tonelada menos recursos si lo hace. Si comienza a tratarlo como algo más importante, se sentirá mucho más incorrecto querer burlarse de él y ahora el controlador de mensajes comenzará a ser lo que está inyectando, no el cliente.
En otras palabras, diseñe sus dependencias alrededor del controlador en lugar del cliente. Aún mejor, "servicios" abstractos que usan HttpClient que le permiten inyectar un controlador y usarlo como su dependencia inyectable. Luego, en sus pruebas, puede falsificar el controlador para controlar la respuesta para configurar sus pruebas.
Envolver HttpClient es una loca pérdida de tiempo.
Actualización: vea el ejemplo de Joshua Dooms. Es exactamente lo que recomiendo.
fuente
Como también se menciona en los comentarios, debe resumir el
HttpClientpara no acoplarse a él. He hecho algo similar en el pasado. Intentaré adaptar lo que hice a lo que estás intentando hacer.Primero mira el
HttpClientclase y decida qué funcionalidad proporciona que sería necesaria.Aquí hay una posibilidad:
Nuevamente, como se dijo antes, esto fue para fines particulares. Abstraí completamente la mayoría de las dependencias de cualquier cosa relacionada con
HttpClienty me concentré en lo que quería devolver. Debe evaluar cómo desea abstraer elHttpClientpara proporcionar solo la funcionalidad necesaria que desea.Esto ahora le permitirá simular solo lo que se necesita para probar.
Incluso recomendaría eliminar por
IHttpHandlercompleto y usar laHttpClientabstracciónIHttpClient. Pero simplemente no estoy eligiendo, ya que puede reemplazar el cuerpo de la interfaz de su controlador con los miembros del cliente abstraído.IHttpClientLuego, se puede usar una implementación de para envolver / adaptar un objeto real / concretoHttpCliento cualquier otro objeto, que se puede usar para realizar solicitudes HTTP, ya que lo que realmente deseaba era un servicio que brindara esa funcionalidad en relación conHttpClientespecífico. El uso de la abstracción es un enfoque limpio (Mi opinión) y SÓLIDO y puede hacer que su código sea más fácil de mantener si necesita cambiar el cliente subyacente por otra cosa a medida que cambia el marco.A continuación, se muestra un fragmento de cómo se podría realizar una implementación.
Como puede ver en el ejemplo anterior, gran parte del trabajo pesado generalmente asociado con el uso
HttpClientestá oculto detrás de la abstracción.Su clase de conexión puede luego inyectarse con el cliente abstraído
Luego, su prueba puede simular lo que se necesita para su IVU
fuente
HttpClienty un adaptador para crear su instancia específica usandoHttpClientFactory. Hacer esto hace que probar la lógica más allá de la solicitud HTTP sea trivial, que es el objetivo aquí.Sobre la base de las otras respuestas, sugiero este código, que no tiene dependencias externas:
fuente
HttpMessageHandlerusted mismo lo hace casi imposible, y tiene que hacerlo porque los métodos lo sonprotected internal.Creo que el problema es que lo tienes un poco al revés.
Si miras la clase anterior, creo que esto es lo que quieres. Microsoft recomienda mantener vivo al cliente para un rendimiento óptimo, por lo que este tipo de estructura le permite hacerlo. Además, HttpMessageHandler es una clase abstracta y, por lo tanto, se puede simular. Su método de prueba se vería así:
Esto le permite probar su lógica mientras se burla del comportamiento de HttpClient.
Lo siento chicos, después de escribir esto y probarlo yo mismo, me di cuenta de que no pueden burlarse de los métodos protegidos en HttpMessageHandler. Posteriormente agregué el siguiente código para permitir la inyección de una simulación adecuada.
Las pruebas escritas con esto se parecen a lo siguiente:
fuente
Uno de mis colegas notó que la mayoría de los
HttpClientmétodos llamanSendAsync(HttpRequestMessage request, CancellationToken cancellationToken)bajo el capó, que es un método virtual deHttpMessageInvoker:Entonces, con mucho, la forma más fácil de burlarse
HttpClientfue simplemente burlarse de ese método en particular:y su código puede llamar a la mayoría (pero no a todos) de los
HttpClientmétodos de clase, incluido unMarque aquí para confirmar https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs
fuente
SendAsync(HttpRequestMessage)directamente. Si puede modificar su código para no usar esta función de conveniencia, entonces burlarse de HttpClient directamente anulandoSendAsynces en realidad la solución más limpia que he encontrado.Una alternativa sería configurar un servidor HTTP stub que devuelva respuestas enlatadas según el patrón que coincida con la URL de la solicitud, lo que significa que prueba las solicitudes HTTP reales, no las simulaciones. Históricamente, esto habría requerido un esfuerzo de desarrollo significativo y habría sido demasiado lento para ser considerado para pruebas unitarias, sin embargo, la biblioteca de OSS WireMock.net es fácil de usar y lo suficientemente rápida como para ejecutarse con muchas pruebas, por lo que puede valer la pena considerarla. La configuración consta de unas pocas líneas de código:
Puede encontrar más detalles y orientación sobre el uso de wiremock en las pruebas aquí.
fuente
Aquí hay una solución simple que funcionó bien para mí.
Usando la biblioteca simulada de moq.
Fuente: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/
fuente
SendAsynctodos modos, por lo que no se requiere una configuración adicional.No me convencen muchas de las respuestas.
En primer lugar, imagine que desea realizar una prueba unitaria de un método que utiliza
HttpClient. No debe crearHttpClientuna instancia directamente en su implementación. Debe inyectar una fábrica con la responsabilidad de proporcionar una instancia deHttpClientpara usted. De esa manera, puede burlarse más tarde de esa fábrica y devolver loHttpClientque desee (por ejemplo: un simulacroHttpClienty no la real).Entonces, tendrías una fábrica como la siguiente:
Y una implementación:
Por supuesto, necesitaría registrar esta implementación en su contenedor IoC. Si usa Autofac sería algo como:
Ahora tendría una implementación adecuada y comprobable. Imagina que tu método es algo como:
Ahora la parte de prueba.
HttpClientextiendeHttpMessageHandler, que es abstracto. Creemos un "simulacro" deHttpMessageHandlerque acepte un delegado para que cuando usemos el simulacro también podamos configurar cada comportamiento para cada prueba.Y ahora, y con la ayuda de Moq (y FluentAssertions, una biblioteca que hace que las pruebas unitarias sean más legibles), tenemos todo lo necesario para realizar pruebas unitarias en nuestro método PostAsync que usa
HttpClientObviamente, esta prueba es una tontería, y realmente estamos probando nuestro simulacro. Pero se entiende la idea. Debe probar la lógica significativa en función de su implementación, como ..
El propósito de esta respuesta fue probar algo que usa HttpClient y esta es una forma limpia y agradable de hacerlo.
fuente
Unirme a la fiesta un poco tarde, pero me gusta usar wiremock ( https://github.com/WireMock-Net/WireMock.Net ) siempre que sea posible en la integración de un microservicio de dotnet core con dependencias REST descendentes.
Al implementar un TestHttpClientFactory que extiende el IHttpClientFactory, podemos anular el método
HttpClient CreateClient (nombre de cadena)
Entonces, cuando use los clientes nombrados dentro de su aplicación, usted tiene el control de devolver un HttpClient conectado a su wiremock.
Lo bueno de este enfoque es que no está cambiando nada dentro de la aplicación que está probando y habilita las pruebas de integración del curso haciendo una solicitud REST real a su servicio y burlándose del json (o lo que sea) que debería devolver la solicitud descendente real. Esto conduce a pruebas concisas y la menor cantidad de burlas posible en su aplicación.
y
fuente
Dado que
HttpClientusa elSendAsyncmétodo para realizar todoHTTP Requests, puede usar eloverride SendAsyncmétodo y burlarse delHttpClient.Para esa envoltura creando
HttpClientainterface, algo como abajoLuego, use arriba
interfacepara la inyección de dependencia en su servicio, muestra a continuaciónAhora, en el proyecto de prueba unitaria, cree una clase de ayuda para burlarse
SendAsync. Aquí hay unaFakeHttpResponseHandlerclase queinheritingDelegatingHandlerproporcionará una opción para anular elSendAsyncmétodo. Después de anular elSendAsyncmétodo, es necesario configurar una respuesta para cada métodoHTTP Requestque está llamandoSendAsync, para eso, cree unDictionarywithkeyasUriyvalueasHttpResponseMessagepara que siempre que haya unHTTP Requesty si lasUricoincidenciasSendAsyncdevuelvan el configuradoHttpResponseMessage.Cree una nueva implementación para
IServiceHelperburlarse del marco o como a continuación. EstaFakeServiceHelperclase la podemos usar para inyectar laFakeHttpResponseHandlerclase de modo que siempre que seaHttpClientcreada por estaclassse use enFakeHttpResponseHandler classlugar de la implementación real.Y en la configuración de prueba
FakeHttpResponseHandler classagregando elUriy esperadoHttpResponseMessage. ElUridebe ser el realservicepunto finalUride manera que cuando eloverridden SendAsyncmétodo es llamado desde realserviceaplicación que coincidirá con elUrideDictionaryy responder con el configuradoHttpResponseMessage. Después de configurar, inyecteFakeHttpResponseHandler objecta laIServiceHelperimplementación falsa . Luego inyecte elFakeServiceHelper classal servicio real que hará que el servicio real utilice eloverride SendAsyncmétodo.Enlace de GitHub: que tiene una implementación de muestra
fuente
Puede usar la biblioteca RichardSzalay MockHttp que simula el HttpMessageHandler y puede devolver un objeto HttpClient para usarlo durante las pruebas.
GitHub MockHttp
PM> Paquete de instalación RichardSzalay.MockHttp
De la documentación de GitHub
Ejemplo de GitHub
fuente
Esta es una pregunta antigua, pero siento la necesidad de ampliar las respuestas con una solución que no vi aquí.
Puede falsificar el ensamblaje de Microsoft (System.Net.Http) y luego usar ShinsContext durante la prueba.
Depende de su implementación y prueba, sugeriría implementar todas las acciones deseadas donde llama a un método en HttpClient y desea falsificar el valor devuelto. El uso de ShimHttpClient.AllInstances falsificará su implementación en todas las instancias creadas durante su prueba. Por ejemplo, si desea falsificar el método GetAsync (), haga lo siguiente:
fuente
Hice algo muy simple, ya que estaba en un entorno de DI.
y el simulacro es:
fuente
Todo lo que necesita es una versión de prueba de la
HttpMessageHandlerclase que pasa aHttpClientctor. El punto principal es que suHttpMessageHandlerclase de prueba tendrá unHttpRequestHandlerdelegado que las personas que llaman pueden configurar y simplemente manejar deHttpRequestla manera que quieran.Puede usar una instancia de esta clase para crear una instancia concreta de HttpClient. A través del delegado de HttpRequestHandler, tiene control total sobre las solicitudes http salientes de HttpClient.
fuente
Inspirado por la respuesta de PointZeroTwo , aquí hay una muestra que usa NUnit y FakeItEasy .
SystemUnderTesten este ejemplo es la clase que desea probar; no se proporciona contenido de muestra para ella, ¡pero supongo que ya lo tiene!fuente
Quizás haya algún código para cambiar en su proyecto actual, pero para proyectos nuevos debería considerar absolutamente usar Flurl.
https://flurl.dev
Es una biblioteca de cliente HTTP para .NET con una interfaz fluida que habilita específicamente la capacidad de prueba del código que la usa para realizar solicitudes HTTP.
Hay muchos ejemplos de código en el sitio web, pero en pocas palabras, lo usa así en su código.
Agregue los usos.
Envíe una solicitud de obtención y lea la respuesta.
En las pruebas unitarias Flurl actúa como un simulacro que se puede configurar para que se comporte como se desee y también para verificar las llamadas que se realizaron.
fuente
Después de buscar cuidadosamente, descubrí el mejor enfoque para lograr esto.
fuente
Para agregar mis 2 centavos. Para simular métodos de solicitud http específicos, ya sea Get o Post. Esto funcionó para mí.
fuente