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
HttpClient
en su interfaz es donde está el problema. Estás obligando a tu cliente a utilizar laHttpClient
clase concreta. En su lugar, debe exponer una abstracción deHttpClient
.Respuestas:
Su interfaz expone la
HttpClient
clase concreta , por lo tanto, cualquier clase que use esta interfaz está vinculada a ella, esto significa que no se puede burlar de ella.HttpClient
no 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
HttpClientHandler
crea el suyo propioHttpClient
, entonces, por supuesto, podría crear múltiples clases que se implementanIHttpHandler
de 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 siHttpClient
tuviera una interfaz de la que pudiera burlarse, desafortunadamente no es así.Sin embargo, este ejemplo no es el billete dorado.
IHttpHandler
todavía confía enHttpResponseMessage
, que pertenece alSystem.Net.Http
espacio de nombres, por lo tanto, si necesita otras implementaciones además deHttpClient
, tendrá que realizar algún tipo de mapeo para convertir sus respuestas enHttpResponseMessage
objetos. 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
IHttpHandler
sin tener que preocuparse por laHttpClient
clase 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
HttpMessageHandler
paso 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.HttpClientFactory
como 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
HttpClient
para 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
HttpClient
clase 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
HttpClient
y me concentré en lo que quería devolver. Debe evaluar cómo desea abstraer elHttpClient
para proporcionar solo la funcionalidad necesaria que desea.Esto ahora le permitirá simular solo lo que se necesita para probar.
Incluso recomendaría eliminar por
IHttpHandler
completo y usar laHttpClient
abstracció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.IHttpClient
Luego, se puede usar una implementación de para envolver / adaptar un objeto real / concretoHttpClient
o 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 conHttpClient
especí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
HttpClient
está 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
HttpClient
y 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
HttpMessageHandler
usted 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
HttpClient
mé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
HttpClient
fue simplemente burlarse de ese método en particular:y su código puede llamar a la mayoría (pero no a todos) de los
HttpClient
mé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 anulandoSendAsync
es 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
SendAsync
todos 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 crearHttpClient
una instancia directamente en su implementación. Debe inyectar una fábrica con la responsabilidad de proporcionar una instancia deHttpClient
para usted. De esa manera, puede burlarse más tarde de esa fábrica y devolver loHttpClient
que desee (por ejemplo: un simulacroHttpClient
y 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.
HttpClient
extiendeHttpMessageHandler
, que es abstracto. Creemos un "simulacro" deHttpMessageHandler
que 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
HttpClient
Obviamente, 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
HttpClient
usa elSendAsync
método para realizar todoHTTP Requests
, puede usar eloverride SendAsync
método y burlarse delHttpClient
.Para esa envoltura creando
HttpClient
ainterface
, algo como abajoLuego, use arriba
interface
para 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 unaFakeHttpResponseHandler
clase queinheriting
DelegatingHandler
proporcionará una opción para anular elSendAsync
método. Después de anular elSendAsync
método, es necesario configurar una respuesta para cada métodoHTTP Request
que está llamandoSendAsync
, para eso, cree unDictionary
withkey
asUri
yvalue
asHttpResponseMessage
para que siempre que haya unHTTP Request
y si lasUri
coincidenciasSendAsync
devuelvan el configuradoHttpResponseMessage
.Cree una nueva implementación para
IServiceHelper
burlarse del marco o como a continuación. EstaFakeServiceHelper
clase la podemos usar para inyectar laFakeHttpResponseHandler
clase de modo que siempre que seaHttpClient
creada por estaclass
se use enFakeHttpResponseHandler class
lugar de la implementación real.Y en la configuración de prueba
FakeHttpResponseHandler class
agregando elUri
y esperadoHttpResponseMessage
. ElUri
debe ser el realservice
punto finalUri
de manera que cuando eloverridden SendAsync
método es llamado desde realservice
aplicación que coincidirá con elUri
deDictionary
y responder con el configuradoHttpResponseMessage
. Después de configurar, inyecteFakeHttpResponseHandler object
a laIServiceHelper
implementación falsa . Luego inyecte elFakeServiceHelper class
al servicio real que hará que el servicio real utilice eloverride SendAsync
mé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
HttpMessageHandler
clase que pasa aHttpClient
ctor. El punto principal es que suHttpMessageHandler
clase de prueba tendrá unHttpRequestHandler
delegado que las personas que llaman pueden configurar y simplemente manejar deHttpRequest
la 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 .
SystemUnderTest
en 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