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/
