Saltar al contenido

Mocking HttpClient en pruebas unitarias

Posterior a investigar en varios repositorios y sitios webs de internet al final dimos con la respuesta que te enseñaremos a continuación.

Solución:

La extensibilidad de HttpClient radica en el HttpMessageHandler pasado al constructor. Su intención es permitir implementaciones específicas de la plataforma, pero también puede burlarse de ella. No es necesario crear un contenedor de decorador para HttpClient.

Si prefiere un DSL a usar Moq, tengo una biblioteca en GitHub / Nuget que facilita un poco las cosas: https://github.com/richardszalay/mockhttp

var mockHttp = new MockHttpMessageHandler();

// Setup a respond for the user api (including a wildcard in the URL)
mockHttp.When("http://localost/api/user/*")
        .Respond("application/json", "'name' : 'Test McGee'"); // Respond with JSON

// Inject the handler or client into your application code
var client = new HttpClient(mockHttp);

var response = await client.GetAsync("http://localhost/api/user/1234");
// or without async: var response = client.GetAsync("http://localhost/api/user/1234").Result;

var json = await response.Content.ReadAsStringAsync();

// No network connection required
Console.Write(json); // 'name' : 'Test McGee'

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 instanciarse una vez y reutilizarse durante la vida útil de una aplicación.

(Fuente).

Burlarse de HttpMessageHandler puede ser un poco complicado porque SendAsync está protegido. Aquí hay un ejemplo completo, usando xunit y Moq.

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Moq.Protected;
using Xunit;
// Use nuget to install xunit and Moq

namespace MockHttpClient 
    class Program 
        static void Main(string[] args) 
            var analyzer = new SiteAnalyzer(Client);
            var size = analyzer.GetContentSize("http://microsoft.com").Result;
            Console.WriteLine($"Size: size");
        

        private static readonly HttpClient Client = new HttpClient(); // Singleton
    

    public class SiteAnalyzer 
        public SiteAnalyzer(HttpClient httpClient) 
            _httpClient = httpClient;
        

        public async Task GetContentSize(string uri)
        
            var response = await _httpClient.GetAsync( uri );
            var content = await response.Content.ReadAsStringAsync();
            return content.Length;
        

        private readonly HttpClient _httpClient;
    

    public class SiteAnalyzerTests 
        [Fact]
        public async void GetContentSizeReturnsCorrectLength() 
            // Arrange
            const string testContent = "test content";
            var mockMessageHandler = new Mock();
            mockMessageHandler.Protected()
                .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny())
                .ReturnsAsync(new HttpResponseMessage 
                    StatusCode = HttpStatusCode.OK,
                    Content = new StringContent(testContent)
                );
            var underTest = new SiteAnalyzer(new HttpClient(mockMessageHandler.Object));

            // Act
            var result = await underTest.GetContentSize("http://anyurl");

            // Assert
            Assert.Equal(testContent.Length, result);
        
    

Tu interfaz expone el concreto HttpClient class, 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 como un decorador patrón:

public interface IHttpHandler

    HttpResponseMessage Get(string url);
    HttpResponseMessage Post(string url, HttpContent content);
    Task GetAsync(string url);
    Task PostAsync(string url, HttpContent content);

Y tu clase se verá así:

public class HttpClientHandler : IHttpHandler

    private HttpClient _client = new HttpClient();

    public HttpResponseMessage Get(string url)
    
        return GetAsync(url).Result;
    

    public HttpResponseMessage Post(string url, HttpContent content)
    
        return PostAsync(url, content).Result;
    

    public async Task GetAsync(string url)
    
        return await _client.GetAsync(url);
    

    public async Task PostAsync(string url, HttpContent content)
    
        return await _client.PostAsync(url, content);
    

El punto en todo esto es que HttpClientHandler crea su propio HttpClient, puede, por supuesto, crear varias clases que implementen IHttpHandler En maneras diferentes.

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 hereda de HttpClient (Ver El ejemplo de Nkosi, es un enfoque mucho mejor que el mío). La vida sería mucho más fácil si HttpClient tenía una interfaz de la que se podía burlar, pero lamentablemente no la tiene.

Este ejemplo es no el boleto dorado sin embargo. IHttpHandler todavía se basa en HttpResponseMessage, que pertenece a System.Net.Http espacio de nombres, por lo tanto, si necesita otras implementaciones además de HttpClient, tendrá que realizar algún tipo de mapeo para convertir sus respuestas en HttpResponseMessage objetos. Esto, por supuesto, es solo un problema. si necesita utilizar varias implementaciones de IHttpHandler pero no parece que tú lo hagas, así que no es el fin del mundo, pero es algo en lo que pensar.

De todos modos, simplemente puedes burlarte IHttpHandler sin tener que preocuparte por el hormigón HttpClient class ya que se ha abstraído.

Recomiendo probar el no asincrónico métodos, ya que todavía llaman a los métodos asincrónicos pero sin la molestia de tener que preocuparse por los métodos asincrónicos de prueba unitaria, consulte aquí

valoraciones y comentarios

Acuérdate de que tienes concesión de decir si te fue de ayuda.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *