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.
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/