Executando verificação de segurança...
1

🧪 Dominando a Arte dos Testes de Unidade em .NET com xUnit, Moq, Fluent Assertions, Bogus e Verify ⚗️

Testes de Unidade são uma ferramenta essencial para garantir que o código se comporte como esperado, ao mesmo tempo que promovem confiança nas mudanças e refatorações. Neste artigo, vamos explorar como criar testes unitários eficientes usando alguns pacotes populares no ecossistema .NET. Além disso, veremos como estruturar testes para um projeto que segue a Clean Architecture.

O código completo deste exemplo está disponível no GitHub.


Introdução aos testes de Unidade

Testes unitários são responsáveis por validar o comportamento de pequenas unidades de código (geralmente métodos ou classes). A ideia é isolar a unidade sendo testada, garantindo que ela funcione de forma independente de outras partes do sistema. Com a ajuda de ferramentas como mocks, as dependências externas podem ser substituídas por implementações controladas.

A seguir, apresentaremos os pacotes usados neste artigo e como cada um contribui para o processo de testes.


Pacotes usados neste exemplo

1. xUnit

O xUnit é um framework de testes popular no .NET. Ele fornece atributos como [Fact] e [Theory] para definir testes, além de uma integração simples com ferramentas de execução de testes como o Visual Studio e o CLI do .NET.

Exemplo:

[Fact]
public async Task Handle_ShouldCreateCustomer_WhenCommandIsValid()
{
    // Teste aqui!
}

2. Moq

Moq é uma biblioteca de mocking que permite criar objetos simulados para substituir dependências em testes de unidade. Com ele, podemos configurar comportamentos esperados para métodos e verificar se eles foram chamados.

No exemplo, usamos o Moq para simular um repositório:

_repositoryMock.Setup(x => x.Add(It.Is<Customer>(c => Compare(c, customer)), CancellationToken.None))
    .ReturnsAsync(true);

3. Fluent Assertions

O Fluent Assertions é usado para criar asserções legíveis e expressivas nos testes. Ele facilita a validação de valores, objetos e exceções.

Exemplo de comparação:

actual.Should().BeEquivalentTo(expected, options => options.Excluding(c => c.Id));

Neste projeto, o Fluent Assertions é usado principalmente no Comparer para objetos complexos como Customer, porque comparações padrão verificariam apenas referências.

4. Verify

O Verify é uma biblioteca de snapshot testing. Ele salva uma versão esperada do resultado de um teste (snapshot) e compara com os resultados futuros. Se houver discrepâncias, o teste falha, e uma ferramenta de comparação de arquivos é aberta para revisar e ajustar os snapshots.

Primeira execução:

  • Um snapshot é gerado e o teste falha porque ainda não existe um de comparação.
  • O desenvolvedor ajusta as diferenças e salva o snapshot.
  • Nas próximas execuções, os resultados são comparados com o snapshot salvo.

5. Bogus

Bogus é uma biblioteca para gerar dados fictícios. É usada para criar dados de teste consistentes e realistas, como nomes, datas, e-mails, etc.

No exemplo, usamos Bogus para criar instâncias da entidade Customer:

var customer = new Faker<Customer>()
    .CustomInstantiator(f => new Customer(
        f.Name.FirstName(),
        f.Name.LastName(),
        f.Internet.Email(),
        DateOnly.FromDateTime(f.Date.PastOffset(70, DateTime.Now.AddYears(-18)).Date),
        f.PickRandom<Gender>()
    ))
    .Generate();

Exemplo de Código: Criando e Testando um Command Handler

O Command Handler

No exemplo, seguimos a Clean Architecture, onde os Command Handlers ficam encapsulados na camada de aplicação e não são expostos diretamente à API. Eles são chamados pelo Mediator, reduzindo o acoplamento.

Para que os testes acessem classes internal, adicionamos a configuração abaixo no projeto de aplicação:

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
        <_Parameter1>Application.Tests</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

Estrutura do Teste

Setup com Moq e Comparer

Como Customer é um tipo complexo, precisamos de um comparer para garantir que a comparação seja feita pelas propriedades, e não por referência:

private static bool Compare<T>(T actual, T expected) where T : IEntity
{
    actual.Should().BeEquivalentTo(expected, options => options.Excluding(c => c.Id));
    return true;
}

Testando o Comportamento do Handler

  • Configuramos o mock do repositório para esperar um Customer equivalente ao comando.
  • Usamos Verify para validar a resposta.

Exemplo:

var sut = new CreateCustomerCommandHandler(_repositoryMock.Object);
var response = await sut.Handle(command, CancellationToken.None);

_repositoryMock.Verify(x => x.Add(It.Is<Customer>(c => Compare(c, customer)), CancellationToken.None), Times.Once);
await Verify(response, verifySettings);

Lidando com Randomização nos Snapshots

Para campos com valores aleatórios, como nomes ou GUIDs, usamos VerifySettings para aplicar scrubs:

var verifySettings = new VerifySettings();
verifySettings.ScrubMember<CreateCustomerResponse>(f => f.FirstName);
verifySettings.ScrubMember<CreateCustomerResponse>(f => f.LastName);

Lidando com Randomização nos Snapshots

Aqui está o fluxo de como um teste unitário é executado, utilizando as ferramentas mencionadas:

flowchart TD
    Start[Teste Iniciado] --> Arrange[Arrange: Configurar Ambiente de Teste]
    Arrange --> |Gerar Mocks| Moq[Moq: Dependências Falsas]
    Arrange --> |Gerar Dados| Bogus[Bogus: Dados de Entrada]
    Arrange --> TargetMethod[Executar Método a Ser Testado]
    TargetMethod --> Assert[Assert: Verificar Resultados]
    Assert --> |Valores Simples| FluentAssertions[Fluent Assertions]
    Assert --> |Saídas Complexas| Verify[Verify: Comparação Avançada]
    FluentAssertions --> End[Teste Finalizado]
    Verify --> End

Configurações do Verify no Git

.gitignore

Adicionamos estas entradas para ignorar arquivos temporários criados pelo Verify:

# Verify
*.received.*
*.received/

.gitattributes

Adicionamos configurações para normalizar os arquivos de snapshot:

# Verify
*.verified.txt text eol=lf working-tree-encoding=UTF-8

O método VerifyChecks.Run() valida essas configurações e sugere ajustes quando necessário:

[Fact]
public Task VerifyCheck() => VerifyChecks.Run();

Nomeação de Testes

Existem dois padrões principais de nomenclatura:

  1. Given... When... Then...
    • Exemplo: GivenValidCommand_WhenHandleIsCalled_ThenCustomerIsCreated.
  2. Action... Should... When...
    • Exemplo: Handle_ShouldCreateCustomer_WhenCommandIsValid.

Ambos são válidos, e a escolha depende do time ou projeto.


Conclusão

Combinando xUnit, Moq, Fluent Assertions, Verify e Bogus, criamos testes unitários poderosos, legíveis e robustos. Este exemplo mostrou como lidar com desafios como comparação de objetos complexos e valores aleatórios, enquanto aproveitamos o poder do Verify para snapshot testing.

O código completo deste exemplo pode ser encontrado no GitHub. Agora é sua vez de colocar a mão na massa e aprimorar seus testes!

Carregando publicação patrocinada...
1
1

O que aconteceu foi que este já é meu terceiro email pois tive a conta banida duas vezes! Pelo que me parece o Tabnews não gosta muito de quem da vote down nos artigos. Era melhor remover a funcionalidade do que punir os usuários (ou impedir comportamentos que sejam considerados contra a politica de uso). Banir o usuário sem qualquer aviso prévio eu acho um pouco exagerado.

1

Não acho que seja bem isso, até porque eu dou bastante negativos, inclusive acho que dei em alguns lugares que você também deu, já que tem postagem que a pessoa viaja demais na maionese. Não sei exatamente o que aconteceu na primeira vez, mas as vezes seguintes pode ser porque você está criando nova para burlar o banimento. Eu não tenho informações privilegiadas e não posso afirmar se o primeiro foi justo ou não, mas eu duvido que seja só por causa dos negativos. Pode ser que tenha sido um padrão de como deu esses negativos que pode ter sido interpretado como vingança. A não ser que seja algo muito grave concordo que banir sem advertência ou suspensão é exagerado. Mas se é a regra do site tem que aceitar ou não usar.

Tente manter o comportamento esperado, peça desculpas, reconheça o erro, e peça para ter mais uma chance, que você aprendeu e quer ter uma participação mais positiva. Mesmo que considere injusto, é o melhor a fazer se quiser continuar participando.

0