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 Task SomePost(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 Task GetContent()
{
	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:

https://github.com/Tomamais/dotnet_mocking_httpclient

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:

ABCDE
1ÁrvoreAlturaIdadeRendimentoLucro
2Maçã182014$105
3Pera121210$96
4Cereja13149$105
5Maçã141510$75
6Pera988$77
7Maçã896$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:


ABCDEF
1ÁrvoreAlturaIdadeRendimentoLucroAltura
2=Maçã>10<16
3=Pera
4
5ÁrvoreAlturaIdadeRendimentoLucro
6Maçã182014$105
7Pera121210$96
8Cereja13149$105
9Maçã141510$75
10Pera988$77
11Maçã896$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ÉDIARetorna a média das entradas selecionadas de um banco de dados
BDCONTARConta as células que contêm números em um banco de dados
BDCONTARAConta células não vazias em um banco de dados
BDEXTRAIRExtrai de um banco de dados um único registro que corresponde a um critério específico
BDMÁXRetorna o valor máximo de entradas selecionadas de um banco de dados
BDMÍNRetorna o valor mínimo de entradas selecionadas de um banco de dados
BDMULTIPLMultiplica os valores em um campo específico de registros que correspondem ao critério em um banco de dados
BDESTEstima o desvio padrão com base em uma amostra de entradas selecionadas de um banco de dados
BDDESVPACalcula o desvio padrão com base na população inteira de entradas selecionadas de um banco de dados
BDSOMASoma os números na coluna de campos de registros do banco de dados que correspondem ao critério
BDVARESTEstima a variação com base em uma amostra de entradas selecionadas de um banco de dados
BDVARPCalcula a variação com base na população inteira de entradas selecionadas de um banco de dados