C#

C# - Testando requisições com Flurl

Implemente testes unitários em requisições HTTP com o auxílio de Flurl.

há 3 anos 2 meses

Formação Desenvolvedor C#
Conheça a formação em detalhes

No meu artigo passado, demostrei a facilidade do uso da biblioteca Flurl no consumo de dados de uma API. Entretanto o ponto de maior orgulho desta biblioteca é a facilidade em testar as requisições criadas com ela.

Testes de unidades em requisições HTTP

Testar classes que fazem requisições HTTP pode ser um ponto complexo do projeto. Ele precisa ser desenvolvido de forma que aceite um Mock ou fazer uso de uma API de testes.

Uma API de testes pode ser uma boa escolha, mas caso ocorra algo com a mesma, os testes do projeto irão falhar. Enquanto o problema com a API não for resolvido, estes testes não poderão ser realizados.

Então, o mais comum é o uso de Mock, que deve ser implementado de acordo com a biblioteca de requisições implementada, já que a classe HttpClient não pode ser “mockada”. Ela não define interfaces que facilitariam este processo.

Dentre as bibliotecas de requisição que permitem o uso de Mock, a Flurl é a que torna este processo o mais transparente e simples.

Definindo testes de unidade com Flurl

Para exemplificar, serão implementados testes de unidade no projeto apresentado no meu artigo anterior. Nele vimos a implementação do Flurl na camada repository do projeto, assim, os testes de unidade mostrados aqui serão realizados apenas nesta camada.

Os testes podem ser feito graças a classe HttpTest da biblioteca. Recomenda-se que ela seja declarada em um bloco using:

using (var httpTest = new HttpTest()) {
    // Realizar as requisições aqui
}

Desta forma, todas as requisições realizadas dentro deste bloco serão interceptadas pela classe e com isso, poderão ser “mockadas”:

public async Task TestListProducts()
{
    var repository = new ProductRepository();
    using (var httpTest = new HttpTest()) {
        // arrange
        httpTest.RespondWith("[{\"id\":\"12d3d23\",\"name\":\"Mouse\", \"quantity\":10, \"price\": 99.9}, {\"id\":\"213drwa3\",\"name\":\"Teclado\", \"quantity\":20, \"price\": 149.9}]");

        // act
        await repository.FindAll();

        // assert
        httpTest
            .ShouldHaveCalled("http://localhost:3002/api/products")
            .WithVerb(HttpMethod.Get);
    }
}

Acima, é definido o mock:

httpTest.RespondWith("[{\"id\":\"12d3d23\",\"name\":\"Mouse\", \"quantity\":10, \"price\": 99.9}, {\"id\":\"213drwa3\",\"name\":\"Teclado\", \"quantity\":20, \"price\": 149.9}]");

Ou seja, não importa qual requisição, será retornado este conteúdo.

Em seguida a requisição é realizada:

await repository.FindAll();

E é verificado se ela foi feita para a API via GET:

httpTest
    .ShouldHaveCalled("http://localhost:3002/api/products")
    .WithVerb(HttpMethod.Get);

Ao executar o teste, ele irá passar:

asciicast

Definindo critérios para as requisições interceptadas

No exemplo anterior todas as requisições realizadas serão interceptadas pela classe HttpTest. Entretanto, é possível refinar isso:

public async Task TestCreateProducts()
{
    var repository = new ProductRepository();
    using (var httpTest = new HttpTest()) {
        // arrange
        httpTest
                .ForCallsTo("http://localhost:3002/*", "https://api.com/*")
                .WithVerb(HttpMethod.Post)
                .RespondWith("[{\"id\":\"12d3d23\",\"name\":\"Mouse\", \"quantity\":10, \"price\": 99.9}", 201);
        
        var product = new Product {
            Name = "Mouse",
            Quantity = 10,
            Price = 99.9
        };

        // act
        await repository.Add(product);

        // assert
        httpTest
            .ShouldHaveCalled("*/api/products")
            .WithRequestBody("{\"Id\":*,\"Name\":\"Mouse\",\"Quantity\":10,\"Price\":99.9}")
            .WithVerb(HttpMethod.Post);
    }
}

Note que é definido as APIs que serão interceptadas e o verbo HTTP:

httpTest
        .ForCallsTo("*localhost:3002/*", "*api.*.com/*")
        .WithVerb(HttpMethod.Post)

Qualquer requisição que não se enquadre nestes critérios será ignorada pela HttpTest.

No assert, se verifica apenas o endpoint:

httpTest
    .ShouldHaveCalled("*/api/products")

Não é necessário informar o servidor, porque os aceitos já estão definidos no critério especificado. Também note que é utilizado curingas (*). Eles podem ser implementados nos parâmetros de todos os métodos da classe.

Caso o teste seja executado, este segundo também passará:

asciicast

Conclusão

Adicionar testes em um projeto é uma boa prática que todos devem adotar. Ao se trabalhar com requisições, este processo pode ser facilitado ao adotar a biblioteca Flurl. Assim, caso esteja trabalhando com requisições e necessite implementar testes, não deixe de verificar esta biblioteca.

Neste artigo não foram abordados todos os métodos da classe HttpTest. Como vários são úteis, não deixe de vê-los na documentação da mesma.

Então é isso, por hoje é só :)

Autor(a) do artigo

Wladimilson M. Nascimento
Wladimilson M. Nascimento

Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.

Todos os artigos

Artigos relacionados Ver todos