Arquivo da categoria: Desenvolvimento

Novidades, críticas, comentários sobre o desenvolvimento e programação de sistemas, assunto que tanto agrada este autor.

Fazendo Mock da Classe HttpClient

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:

https://github.com/Tomamais/dotnet_mocking_httpclient

Selenium – Configurando download automático e caminho do arquivo

Pois é, o Selenium entrou na minha vida de uma maneira que é difícil até em pensar em fazer algo sem ele… ok, exagerei, mas vocês entenderam o que eu quis dizer.

Fazer download de um arquivo é uma tarefa até básica, mas a princípio não há controle de onde o arquivo vai parar ou qual seu nome, nem se vai aparecer aquela caixa de confirmação. Pois bem, ao menos Chrome, isso é relativamente simples de configurar isso a partir de suas preferências, que via Selenium é chamado de Capabilities ou ChromeOptions. Até onde sei o Firefox também suporta isso, mas o Chrome me pareceu mais flexível.

Para definir isso, utiliza-se o método “SetPreference” do driver, que deve ser configurado antes de iniciar a navegação, ou seja, antes de chamar o Get “http://endereco.do.seu.site”.

O trecho de código abaixo configura o Chrome para:

  1. Ativar o download automático (sem pedir confirmação)
  2. Alterar o caminho de download do arquivo para a mesma pasta do arquivo em execução
Private Sub AbreEConfiguraOChrome()
    Dim driver As New Selenium.ChromeDriver
    driver.SetPreference "download.default_directory", Replace(ThisWorkbook.FullName, ThisWorkbook.name, "")
    driver.SetPreference "download.directory_upgrade", True
    driver.SetPreference "download.prompt_for_download", False
    driver.Get "http://endereco.do.seu.site"
    'aqui começa seu código
End Sub

Simples! Ok, demorou um bocando para eu descobrir isso, mas aí está.

Bom proveito!

Selenium – Obtendo o nome do último arquivo baixado no Chrome 70+

Tem alguns dias postei um artigo muito “louco” sobre Selenium, ShadowRoot e Polymer, longo, controverso e cansativo.

Aqui, farei uso de tudo aquilo para resolver um simples problema. Obter o nome do último arquivo baixado no seu Chrome instanciado via Selenium. Sem complicações, vamos ao código:

'Estou assumindo que a variável driver é do WebDriver de está declarada globalmente
Public Function UltimoDownloadFeito() As String
    Dim shadowRoot As WebElement, _
        downloadsManager As WebElement, _
        downloadsItem As WebElement, _
        name As WebElement
 
    Set driver = New ChromeDriver
    driver.Get "chrome://downloads"
 
    Set downloadsManager = driver.FindElementByTag("downloads-manager")
    Set shadowRoot = driver.ExecuteScript("return arguments[0].shadowRoot", downloadsManager)
    Set downloadsItem = driver.ExecuteScript("return arguments[0].querySelectorAll('downloads-item')[0];", shadowRoot)
    Set shadowRoot = driver.ExecuteScript("return arguments[0].shadowRoot", downloadsItem)
    Set name = shadowRoot.FindElementById("name")
 
    UltimoDownloadFeito = name.Text
End Function

É claro, se você executar essa rotina diretamente, vai receber um erro, já que não haverão itens na lista. Fica como desafio implementar uma lógica para tratar esse erro. Para ver funcionar na íntegra, efetue um download antes. Abaixo faço isso com o Modelo de Cadastro:

Public Function UltimoDownloadFeito() As String
    Dim shadowRoot As WebElement, _
        downloadsManager As WebElement, _
        downloadsItem As WebElement, _
        name As WebElement
 
    Set driver = New ChromeDriver
    'faz o download do arquivo antes
    driver.Get "http://www.tomasvasquez.com.br/downloads/ModeloCadastrov3ListView.zip"
    driver.Get "chrome://downloads"
 
    Set downloadsManager = driver.FindElementByTag("downloads-manager")
    Set shadowRoot = driver.ExecuteScript("return arguments[0].shadowRoot", downloadsManager)
    Set downloadsItem = driver.ExecuteScript("return arguments[0].querySelectorAll('downloads-item')[0];", shadowRoot)
    Set shadowRoot = driver.ExecuteScript("return arguments[0].shadowRoot", downloadsItem)
    Set name = shadowRoot.FindElementById("name")
 
    UltimoDownloadFeito = name.Text
End Function

Bom proveito!

PS: Testei esse código na versão 70 do Chrome. Pode ser que ele funcione em algumas versões anteriores e/ou posteriores, mas não garanto. Teste antes de usar, ok? 🙂

Vídeo – VBA, Conhecendo o Rubberduck – Code Explorer, Todo List, Indent e mais

Rubberduck, o plugin que você ainda vai instalar no seu VBA!

🙂

Peço a todos perdão pelo áudio. Ainda estou me acostumando com este novo computador e com esse microfone maluco. Sim, ainda vou comprar um decente.

Acesse também

BLOG ► https://www.tomasvasquez.com.br/blog/
FÓRUM ► http://www.tomasvasquez.com.br/forum/
CURSO ONLINE DE C# ► http://www.tomasvasquez.com.br/cursocsharp

Aqui também!

FACEBOOK ► https://www.facebook.com/tomasvaquezsites
TWITTER ► https://twitter.com/tomamais
GOOGLE+ ► https://plus.google.com/+TomasvasquezBr/

Roteiro, apresentação, edição, etc, etc ► eu mesmo 🙂