Testes unitários e de integração com FastEndpoints

Olá a todos! Neste artigo, você aprenderá como configurar seu projeto .NET para executar testes unitários utilizando o FastEndpoints! 

App Fixture 

Primeiramente, é necessário entender o que é uma Fixture. Dentro do xUnit (um framework de testes para C#), uma Fixture é um objeto compartilhado por todas as classes de teste. Essa Fixture é criada quando um teste inicia e destruída após todos os testes do arquivo serem finalizados. Essa estratégia permite compartilhar recursos entre múltiplos testes de uma mesma classe. 

Quando uma AppFixture é instanciada pelo xUnit para uma classe de teste, ela inicializa uma instância da sua aplicação alvo (SUT) como um servidor de teste em memória/host web. Essa instância será reutilizada por todos os métodos de teste da classe, acelerando a execução dos testes, já que iniciar e encerrar um servidor de aplicação web (WAF/host web) para cada método de teste (ou até mesmo para cada classe de teste) resultaria em uma execução de testes mais lenta. SUT (System Under Test): o sistema ou aplicação que está sendo testado. In-memory test-server/web-host: um servidor de teste que roda na memória, sem precisar ser executado em um servidor físico ou em um ambiente de rede. (editado) 

Em resumo: Uma app fixture compartilha informações. O objeto aqui, é criar usuários na sua aplicação com diferentes perfis/cargos, e reutiliza-lós por seus testes. Assim, testando seus endpoints com diferentes niveis de acesso. 

Criando seu projeto de testes 

Para começar, instale o pacote de templates do FastEndpoints: 

dotnet new install FastEndpoints.TemplatePack 

Isso dará acesso a quatro modelos distintos: 

Se o seu projeto já estiver em andamento, você pode utilizar o template “fetest”, que cria apenas o projeto com os testes. Dentro da pasta do seu projeto, execute o seguinte comando: 

dotnet new fetest -n Tests 

Isso criará a seguinte estrutura de arquivos: 

Apague a pasta “Sample” e crie três novas pastas: “IntegrationTests”, “UnitTests” e “Configs”. 

Criando nossa Fixture 

Crie um arquivo Fixture.cs dentro de /Configs e utilize o seguinte código: 

namespace Tests 
{ 
    public class IntegrationTestsFixture : AppFixture<Program> 
    { 
        public HttpClient CitizenClient { get; private set; } = default!; 
        public HttpClient AdminClient { get; private set; } = default!; 
        public HttpClient OtherRoleClient { get; private set; } = default!; 
        public User Admin { get; set; } 
 
        protected override async Task SetupAsync() 
        { 
            // Implementação do SetupAsync 
        } 
 
        protected override void ConfigureServices(IServiceCollection services) 
        { 
            // Configuração dos serviços necessários 
        } 
 
        protected override async Task TearDownAsync() 
        { 
            // Implementação do TearDownAsync 
        } 
    } 
} 

o FastEndpoints utiliza clientes Http para realizar os testes de integração. A ideia aqui, é criarmos nossos diferentes usuários com diferentes permissões, para realizar nossos testes. Ou, centralizar informações que devam ser compartilhadas por todos os testes. 

Utilizando usuários fictícios para testes 

Crie um utilitário para gerar diferentes tipos de usuários utilizando a biblioteca “Faker”. Crie uma pasta chamada “Extensions” e um arquivo chamado UserFakerExtension.cs. O conteúdo será semelhante ao seguinte: 

 

using Bogus; 
 
static class UserFakerExtension 
{ 
    internal static User User(this Faker faker, List<Role> roles, string? password = null) 
    { 
        return new User 
        { 
            FirstName = faker.Name.FirstName(), 
            LastName = faker.Name.LastName(), 
            Email = faker.Internet.Email(), 
            PasswordHash = BCrypt.Net.BCrypt.HashPassword(password ?? faker.Internet.Password()), 
            Active = true, 
            Lockout = false, 
            ProfileImageURL = faker.Image.PicsumUrl(), 
            Roles = roles.Select(r => r.ToString()).ToArray() 
        }; 
    } 
} 

Configurando os clientes HTTP 

No método SetupAsync, você pode configurar os clientes HTTP com os diferentes cargos da seguinte forma: 

protected override async Task SetupAsync() 
   { 
        var crypto = Services.GetRequiredService<ICryptoProvider>(); 
        Citizen = Fake.User([Role.CITIZEN]); 
        await Citizen.SaveAsync(); 
 
        Admin = Fake.User([Role.ADMIN]); 
        await Admin.SaveAsync(); 
 
        OtherRole = Fake.User([Role.OTHER]); 
        await OtherRole.SaveAsync(); 
 
        var adminBearerToken = crypto.GenerateAccessToken(Admin); 
        AdminClient = CreateClient(c => 
        { 
            c.DefaultRequestHeaders.Authorization = new("Bearer", adminBearerToken); 
        });       
 
        var OtherRole = crypto.GenerateAccessToken(OtherRole ); 
        OtherRoleClient = CreateClient(c => 
        { 
            c.DefaultRequestHeaders.Authorization = new("Bearer", culturalManagerbearerToken); 
        }); 
    } 

Nesse exemplo, estou utilizando o serviço de geração de tokens da minha aplicação para criar tokens com base em um usuário. É importante ressaltar que esses usuários devem estar previamente inseridos no seu banco de dados. Após criar um usuário, estou chamando o método SaveAsync, que é específico do MongoDB. Se você estiver utilizando SQL Server com Entity Framework, você pode configurar seu DbContext dentro do método ConfigureServices da sua Fixture. 

Exemplo com Entity FrameworkCore 

 

public class MockDbContext: IDbContextFactory<MeuDbContext> 
{ 
    public MeuDbContextCreateDbContext() 
    { 
        var options = new DbContextOptionsBuilder<MeuDbContext>() 
            .UseInMemoryDatabase($"InMemoryTestDb-{DateTime.Now.ToFileTimeUtc()}") 
            .Options; 
        return new MeuDbContext(options, new MockUserClaimsProvider()); 
    } 
} 

Limpando após os testes 

No método TearDownAsync, é de extrema importância limpar os recursos criados durante os testes: 

protected override async Task TearDownAsync() 
{ 
    CitizenClient.Dispose(); 
    AdminClient.Dispose(); 
    OtherRoleClient.Dispose(); 
     
    await Citizen.DeleteAsync(); 
    await Admin.DeleteAsync(); 
    await OtherRole.DeleteAsync(); 
} 

Criando testes 

Para criar uma classe de teste, crie um arquivo dentro da pasta IntegrationTests. Por exemplo: 

using Features.User.Auth.Authenticate; 
 
public class Tests(IntegrationTestsFixture _fixture) : TestBase<IntegrationTestsFixture> 
{ 
    [Fact] 
    public async Task Authentication_Succeeded() 
    { 
        // Arrange 
        var request = new AuthenticationRequest 
        { 
            Email = Fixture.Admin.Email, 
            Password = Fixture.Admin.Password 
        }; 
 
        // Act 
        var (response, result) = await Fixture.AdminClient.POSTAsync<AuthenticateEndpoint, AuthenticationRequest, AuthenticationResponse>(request); 
 
        // Assert 
        response.IsSuccessStatusCode.Should().BeTrue(); 
        result.AccessToken.Should().NotBeNullOrEmpty(); 
        result.RefreshToken.Should().NotBeNullOrEmpty(); 
    } 
} 

Para mais exemplos como este, você pode acessar diretamente no GitHub do FastEndpoints: FastEndPoints Examples 

Ambiente 

Certifique-se de criar um arquivo appsettings.Testing.json com configurações semelhantes ao development.json. 

Pode-se criar uma Fixture para cada módulo do sistema? 

Sim, cada classe de teste pode herdar de TestBase<T>, onde T pode ser a Fixture específica para o módulo sendo testado. Isso permite configurações específicas e criação de recursos específicos para o módulo em teste. 

Seja o primeiro a comentar

Faça um comentário

Seu e-mail não será divulgado.


*