O problema?
Ainda que você não viva no mundo dos micro-serviços, uma hora você vai precisar chamar um serviço REST em outra ponta via HTTP/S, e no caso do AspNetCore, o jeito mais natural de fazê-lo é via a classe HttpClient.
Até aqui, tudo tranquilo, os métodos Get, Post, Put, Patch e Delete (talvez até outros) estão lá para serem usados. O problema é, caso sua implementação precise passar por testes unitários, a classe HttpClient pode lhe trazer problemas, já que não há interface disponível para mock e os métodos acima citados não são virtual.
Note que citei que ela “pode” lhe trazer problemas. Como fazer então para que a classe HttpClient não seja um para seus testes unitários?
Tudo se resume ao SendAsync
Se há algo que é mágico no AspNetCore é que ele é código aberto. Isso mesmo, você ir lá no github e ter acesso ao código fonte de praticamente 100% das classes que você usa no seu projeto. E, felizmente, esse é a caso do HttpClient. Vou deixar o link do fonte no fim do artigo.
Dando uma lida na classe, que é bem escrita por sinal, percebe-se que todos os métodos REST com exceção do Get, chamam um método chamado SendAsync, que recebe como parâmetro um objeto HttpRequestMessage e opcionalmente um CancellationToken.
O HttpRequestMessage precisa basicamente do corpo que você enviaria via Post, Put, Patch e Delete. O ponto de atenção é que somente a versão com o CancellationToken é virtual/override. Se você sabe o que o CancellationToken é, você deveria estar usando-o. Caso não, use CancellationToken.None por agora.
O código de exemplo abaixo faz um Post usando HttpClient:
public class MeuController : ControllerBase { private readonly HttpClient client; public MeuController(HttpClient client) { this.client = client; } public async Task AlgumPost(Criterios criterios) { // estou ignorando quaisquer configurações de header // também estou assumindo que a classe HttpClient é injetada via construto var stringContent = new StringContent(JsonConvert.SerializeObject(criterios), Encoding.UTF8, "application/json"); var result = await this.client.PostAsync($"url", stringContent, CancellationToken.None); return Ok(result); } }
O código é até simples. Agora, o teste unitário que permite você fazer o “mockar” a chamada PostAsync seria o seguinte:
public async Task DeveriaExecutarOSendAsyncAoMenosUmaVez() { // assumindo o uso do Moq e xUnit var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny(), CancellationToken.None)) .ReturnsAsync(new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent("{}"), }).Verifiable(); var controller = new MeuController( httpClient.Object ); var criterios = new Criterios() { }; var result = await controller.AlgumPost(criterios); httpClient.Verify(x => x.SendAsync(It.IsAny(), CancellationToken.None), Times.Exactly(1)); }
O teste acima passa com sucesso! Note que não estamos fazendo Mock do PostAsync, mas do SendAsync já que o primeiro não pode ser “mockado”, mas ele chama o segundo. Caso queira checar que a chamada tenha sido correta, basta usar o CallBack oferecido pelo Moq.
Mas e o Get/GetString/GetStringAsync?
O que fazer então com os Gets? Simples! Não os use! Digo isso com certeza pois há uma alternativa muito mais limpa para ele. O próprio Send/SendAsync!
O problema dos Gets é que não há um meio apropriado de tratar erros. Se o request falhar, uma Exception pode ser disparada ou não.
O substituto ideal seria o seguinte:
// ao invés disso public async Task ObterConteudo() { var resultado = await this.client.GetStringAsync("https://alguma.api.por.ai"); return Ok(resultado); } // faça isso public async Task ObterConteudo() { var requestMessage = new HttpRequestMessage(HttpMethod.Get, "https://alguma.api.por.ai"); var response = await this.client.SendAsync(requestMessage); if (response.IsSuccessStatusCode) { var resultado = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); return Ok(resultado); } else { return StatusCode((int)response.StatusCode, response.Content.ReadAsStringAsync().Result); } }
Desta forma, o retorno dado ao cliente é muito mais “honesto” e de quebra, você ganha a oportunidade de testá-lo apropriadamente.
Bom proveito!
Código fonte da classe HttpClient:
https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs
Aqui uma implementação de código funcionando: