Sorry, this entry is only available in Português.
(Português) Web Scraping, WhatsApp, CAPTCHA e Ética
Mocking the HttpClient on AspNet Core
The issue?
Even if you don’t live in the microservices world, you will eventually be in need to make a REST call to some API out there, especially in the AspNet Core world, and the best way to do it is through the HttpClient class.
This class gives you pretty much everything you need, Get, Post, Put, Patch and Delete methods, which map one-to-one to the respective HTTP verbs. The problem is, if you need to Unit Test your class, you may have problems as the methods mentioned above aren’t virtual and the HttpClient class inherits from another class that is not related to these.
Well, you might have noticed (have you?) that the subtitle has a question mark. There is a reason for it. If you do the right implementation, HttpClient will not be an issue for your Unit Tests.
Everything lies on SendAsync method
If there is something beautiful in the AspNet Core world is that it is open source. YES! Every single method and property you have access to has its code open the be read at anytime on GitHub, and yes, HttpClient is part of it! I will leave the link to its source code at the bottom.
After looking close at the source code (which is well written by the way), you figure that all methods except the GET ones (Get, GetAsync, GetStringAsync, etc) make usage of the SendAsync. The SendAsync receives as a mandatory parameter a HttpRequestMessage, which is basically what you send through an HTTP call (body, headers, etc). There is an overload that gets an additional CancellationToken parameter, and that’s the one we are looking for because it is overridable. If you know what a CancellationToken is, just use it, if you don’t, just use Cancellation.None for now.
The code below is an example of a POST being done using HttpClient in a common WebApi Controller.
public class MyController : ControllerBase { private readonly HttpClient client; public MyController(HttpClient client) { this.client = client; } public async TaskSomePost(Criteria criteria) { // I am ignoring headers or anything else you might need to send // The HttpClient is injected via constructor var stringContent = new StringContent(JsonConvert.SerializeObject(criteria), Encoding.UTF8, "application/json"); var result = await this.client.PostAsync($"url", stringContent, CancellationToken.None); return Ok(result); } }
The code is fairly simple and it posts something to an endpoint. Now, the unit test that covers and mock HttpClient PostAsync method:
[Fact] public async Task ItShouldCallSendAsyncAtLeastOnceWhenPosts() { // Moq and xUnit are being used here 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 MyController( httpClient.Object ); var criteria = new Criteria() { }; var result = await controller.SomePostPost(criteria); httpClient.Verify(x => x.SendAsync(It.IsAny (), Cancellation.Token), Times.Exactly(1)); }
From what I can tell, the test above passes! Note that I am not mocking PostAsync, but SendAsync as the first one is not mockable the second one is called by the first. In case you want to verify anything sent to the PostAsync, you can use CallBacks offered by Moq lib.
What about Get/GetString/GetStringAsync?
So, what about the “GETs”? Well, it’s simple, don’t use them! Here is why.
The Gets have the advantage of being simple and straightforward, but they only work well for text results and anything bad that happens will throw an exception. Fortunately, there is an alternative. The SendAsync!
Take a look at the code below and it will become more clear:
// instead of this public async TaskGetContent() { var result = await this.client.GetStringAsync("https://some.api"); return Ok(result); } // do this public async Task GetContent() { var requestMessage = new HttpRequestMessage(HttpMethod.Get, "https://some.api"); var response = await this.client.SendAsync(requestMessage); if (response.IsSuccessStatusCode) { var result = JsonConvert.DeserializeObject (await response.Content.ReadAsStringAsync()); return Ok(result); } else { return StatusCode((int)response.StatusCode, response.Content.ReadAsStringAsync().Result); } }
By doing this, your return is more consistent and also gives you the chance for a better error check as the response contains everything you need, like StatusCode, etc.
Enjoy it!
HttpClient source code:
https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs
A working example of it:
Excel – BDSOMA, BDMULTIPL, BD… e as funções do excel que você deveria conhecer
20 anos usando o Excel e ele ainda me surpreende. E sabe o que é mais curioso? Ele te surpreende com recursos que sempre estiveram lá, há anos, muitos, mas você nunca notou!
Este é o caso das funções de Banco de Dados, ou BD*.
Eu tive contato com estas fórmulas no começo da minha vida no Excel, mas, seja pela forma como ela foi explicada ou minha falta de experiência, acabei não sabendo o que fazer com elas e acabei deixando pra lá.
Não vou abordar todas aqui, primeiro, porque uma vez que você aprenda a usar uma, as outras seguem o mesmo raciocínio de uso, tornando fácil a adaptação, segundo, a lista é longa, terceiro… já esqueci, mas vamos ao que interessa.
No fim do artigo tem uma lista de TODAS as funções de banco de dados do próprio site da Microsoft para você conferir.
É hora do show!
Vou usar aqui o BDSOMA. Ele vai resumir o espetáculo que utilizar as funções de banco de dados. Vou aplicar o conceito máximo do reaproveitamento e utilizar o próprio exemplo da Microsoft.
Considere a tabela de dados abaixo:
A | B | C | D | E | |
1 | Árvore | Altura | Idade | Rendimento | Lucro |
2 | Maçã | 18 | 20 | 14 | $105 |
3 | Pera | 12 | 12 | 10 | $96 |
4 | Cereja | 13 | 14 | 9 | $105 |
5 | Maçã | 14 | 15 | 10 | $75 |
6 | Pera | 9 | 8 | 8 | $77 |
7 | Maçã | 8 | 9 | 6 | $45 |
Uma lista de simples de pés-de-alguma fruta, coisa que todo morador de interior já teve no quintal de casa.
Para o exemplo, se quisermos extrair a SOMA dos rendimentos, fica fácil, se quisermos extrair a SOMASE a árvore for de Maçã, também fica fácil. Mas, se quisermos extrair a soma se a árvore for Maçã e os redimentos forem maiores que 10 e menores do que 16, precisamos pensar um pouco.
Se você for um pouco letrado no Excel, sabe que isso se resolve com um SOMASES com a seguinte fórmula:
=SOMASES(D2:D7;B2:B7;”>10″;A2:A7;”=Maçã”;B2:B7;”<16″)
Mas, se complicarmos um pouco mais, gerando critérios múltiplos para nossa soma, você precisa ou elaborar muito o SOMASES ou utilizar mais de um. Por exemplo, se eu quiser incluir a soma de tudo o que for Pera, a fórmula fica assim:
=SOMASES(D2:D7;B2:B7;”>10″;A2:A7;”=Maçã”;B2:B7;”<16″)+SOMASES(D2:D7;A2:A7;”=Pera”)
Resolve, mas é “feio”.
Nada contra o SOMASES, o problema é que, se você precisa repetir uma fórmula, a probabilidade de existir uma solução mais simples é inegável. Outra reclamação pessoal que tenho é que, para entender o que está acontecendo, eu basicamente tenho que decifrar a fórmula, ou seja, entrar na célula e lembrar o que ela está fazendo. Para fórmulas simples, isso não é problema, mas para coisas medonhas que requerem quebras de linha, não é tão simples.
Isso não acontece quando você utiliza o BDSOMA. Vamos aplicar o mesmo critério de soma detalhado acima usando-a. Abaixo, a planilha um pouco transformada:
A | B | C | D | E | F | |
1 | Árvore | Altura | Idade | Rendimento | Lucro | Altura |
2 | =Maçã | >10 | <16 | |||
3 | =Pera | |||||
4 | ||||||
5 | Árvore | Altura | Idade | Rendimento | Lucro | |
6 | Maçã | 18 | 20 | 14 | $105 | |
7 | Pera | 12 | 12 | 10 | $96 | |
8 | Cereja | 13 | 14 | 9 | $105 | |
9 | Maçã | 14 | 15 | 10 | $75 | |
10 | Pera | 9 | 8 | 8 | $77 | |
11 | Maçã | 8 | 9 | 6 | $45 |
Eu já gosto mais do que estou vendo. Em resumo, a tabela principal foi empurrada algumas linhas para baixo e deu espaço ao que vou chamar de área de filtros ou critérios. Ou seja:
A5:E11 – São os dados que queremos trabalhar
A1:F3 – Os filtros a serem aplicados
Usando o BDSOMA, o mesmo resultado do SOMASES pode ser conseguido com a seguinte fórmula:
=BDSOMA(A5:E11;”Rendimento”;A1:F3)
Explicando a fórmula acima:
- A5:E11 é obviamente a área de dados a ser avaliada
- “Rendimento” é a coluna que queremos somar. Sim, você não precisa mencionar índice ou coisa parecida. Basta o nome
- A1:F3 são os filtros, que podem ser repetidos, unidos e cruzados! Veja que temos duas linhas para dizer que queremos árvores de Maçã OU Pera, e no caso da Maçã, com altura >10 (célula B2) e <16 (célula F2)
Não sei para você leitor, mas para mim, isso é mágico! Os filtros estão à mostra e sei EXATAMENTE o que está sendo considerado na soma! E posso mudar à vontade a área de filtros para ver o resultado mudar, sem precisar mexer na fórmula! Experimente!
Sim, você pode chegar num resultado parecido com o SOMASES, mas dá mais trabalho, já que não é natural.
O formato utilizado é só uma convenção, sendo que os filtros podem ser colocar em qualquer lugar do seu arquivo.
“Mas se eu quiser o PRODUTO? Ou a MÉDIA?”. Oh meu caro padawan, divirta-se com a lista abaixo que utiliza a mesma estrutura do BDSOMA:
Função | Descrição |
---|---|
BDMÉDIA | Retorna a média das entradas selecionadas de um banco de dados |
BDCONTAR | Conta as células que contêm números em um banco de dados |
BDCONTARA | Conta células não vazias em um banco de dados |
BDEXTRAIR | Extrai de um banco de dados um único registro que corresponde a um critério específico |
BDMÁX | Retorna o valor máximo de entradas selecionadas de um banco de dados |
BDMÍN | Retorna o valor mínimo de entradas selecionadas de um banco de dados |
BDMULTIPL | Multiplica os valores em um campo específico de registros que correspondem ao critério em um banco de dados |
BDEST | Estima o desvio padrão com base em uma amostra de entradas selecionadas de um banco de dados |
BDDESVPA | Calcula o desvio padrão com base na população inteira de entradas selecionadas de um banco de dados |
BDSOMA | Soma os números na coluna de campos de registros do banco de dados que correspondem ao critério |
BDVAREST | Estima a variação com base em uma amostra de entradas selecionadas de um banco de dados |
BDVARP | Calcula a variação com base na população inteira de entradas selecionadas de um banco de dados |