Mockando apis HttpClient usando XUnit no .NetCore

0

Criar mocks nem sempre é uma tarefa fácil, principalmente se tratando de mocks de API’s ou chamadas diretas à repositórios que normalmente possuem alteração de estado. Neste artigo explicarei como simular o HttpClient usando o XUnit. Sim, já temos algumas poucas maneiras de mockar o httpclient, escrevendo um wrapper para o HttpClient.
Mas há um problema em não cobrir casos de teste para a classe HttpClient, pois sabemos que não há uma interface herdada com o HttpClient. Para lidar com esse caso, introduzimos a propriedade HttpClient na interface. Usando essa propriedade de interface, podemos alcançar o que queremos com a simulação de uma chamada através de um httpclient.  

Como eu já mencionei, o HttpClient não herda de nenhuma interface, então você terá que escrever sua própria. Alguns deles sugerem um padrão de decorator. Então, aqui estamos mockando apenas da classe wrapper e não do httpclient. Então, como podemos mockar o httpclient também? Vamos vê-lo em ação, estou usando o Visual Studio 2019, .Net Core 2.2 e XUnit.  

Para demonstrar o mock de httpclient usando o Xunit, estou criando uma API simples com dados do (openweathermap.org) e adicionando um novo projeto de teste (XUnit).

Vamos criar a interface IHttpClientHelper para mock do httpclient. Aqui você também pode ver a propriedade HttpClient, que é usada para armazenar o objeto HttpMessageHandler mockado. A classe de implementação para IHttpClientHelper é semelhante a seguir:


 public class HttpClientHelper : IHttpClientHelper
    {
        public HttpClient HttpClient { get; set; }

        public async Task<TResult> GetAsync<TResult>(string requestUri)
        {
            TResult objResult = default(TResult);

            using (var client = this.GetHttpClient())
            {
                using (var response = await client.GetAsync(requestUri))
                {
                    if (TryParse<TResult>(response, out objResult))
                    {
                        return objResult;
                    }

                    using (HttpContent content = response.Content)
                    {
                        throw new HttpRequestException(response.Content.ReadAsStringAsync().Result);
                    }
                }
            }
        }
        private HttpClient GetHttpClient()
        {
            if (HttpClient == null)//While mocking we set httpclient object to bypass actual result.
            {
                var _httpClient = new HttpClient();
                _httpClient.DefaultRequestHeaders.Accept.Clear();
                _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                return _httpClient;
            }
            return HttpClient;
        }

        private bool TryParse<TResult>(HttpResponseMessage response, out TResult t)
        {
            if (typeof(TResult).IsAssignableFrom(typeof(HttpResponseMessage)))
            {
                t = (TResult)Convert.ChangeType(response, typeof(TResult));
                return true;
            }

            if (response.IsSuccessStatusCode)
            {
                t = response.Content.ReadAsAsync<TResult>().Result;
                return true;
            }

            t = default(TResult);
            return false;
        }
    }
    


Tudo bem, implementamos a classe wrapper para HttpClient. Agora vamos passar para o projeto Test e escrever casos de teste para o método GetAsync () acima. Vamos ver como mockar o HttpClient usando a propriedade da interface que criamos


 public class HttpClientHelperTest
    {
        protected HttpClientHelper HttpClientHelperUnderTest { get; }
        public HttpClientHelperTest()
        {
            HttpClientHelperUnderTest = new HttpClientHelper();
        }

        /// <summary>
        /// Weather info by city name test cases are resides.
        /// </summary>
        public class GetAsyncHttpHelper : HttpClientHelperTest
        {
            [Fact]
            public async Task When_GetAsync_Returns_Success_Result()
            {
                //Arrange;
                var result = new List<CityInfo>()  {
                     new CityInfo() { baseinfo="stations",timezone=-10800, name="Alfenas" }
                };
                var httpMessageHandler = new Mock<HttpMessageHandler>();
                var fixture = new Fixture();

                // Setup Protected method on HttpMessageHandler mock.
                httpMessageHandler.Protected()
                    .Setup<Task<HttpResponseMessage>>(
                        "SendAsync",
                        ItExpr.IsAny<HttpRequestMessage>(),
                        ItExpr.IsAny<CancellationToken>()
                    )
                    .ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
                    {
                        HttpResponseMessage response = new HttpResponseMessage();
                        response.StatusCode = System.Net.HttpStatusCode.OK;//Setting statuscode
                        response.Content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(result)); // configure your response here
                        response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); //Setting media type for the response
                        return response;
                    });

                var httpClient = new HttpClient(httpMessageHandler.Object);
                httpClient.BaseAddress = fixture.Create<Uri>();

                HttpClientHelperUnderTest.HttpClient = httpClient; //Mocking setting Httphandler object to interface property.

                //Act
                var weatherResult = await HttpClientHelperUnderTest.GetAsync<List<CityInfo>>(string.Empty);

                // Assert
                Assert.NotNull(weatherResult);
            }
        }
    }
    

Aqui você pode ver o mock do HttpMessageHandler e atribuindo-o a um construtor HttpClient. O método SendAsync () é a implementação padrão para todas as ações HttpClient.

Mocking Httpclient Using XUnit In .Net Core

Vamos verificar rapidamente os resultados do caso de teste:

O Swagger da nossa aplicação fica assim:

Existe um método de autenticação (as credenciais estão no appsettings) para gerar um bearer token para você utilizar a API de consulta de tempo por nome de cidade.

Código Fonte: https://github.com/jhomarolo/MockHttpClientOpenweather

Referência https://dejanstojanovic.net/aspnet/2020/march/mocking-httpclient-in-unit-tests-with-moq-and-xunit/   

Compartilhe.

Sobre o autor

Criador do blog Código Simples e com mais 9 anos de experiência em TI, com títulos de MVP Microsoft na área de Visual Studio Development, Neo4j Top 50 Certificate, Scrum Master e MongoDB Evangelist. Atuando em funções analista, desenvolvedor, arquiteto, líder técnico e gestor de equipes. Mais informações em : http://jhonathansoares.com