Testes de Unidade - Usando xUnit e MOQ


   Hoje verermos como criar testes de unidade na plataforma .NET usando o xUnit e o MOQ.

Os t
estes de unidade são testes automatizados realizados em pequenas partes de um software, conhecidas como unidades, como funções ou métodos. Esses testes são projetados para verificar se cada unidade do software funciona conforme o esperado e se ela responde corretamente a diferentes entradas e condições.

Um teste de unidade pode ser considerado como o menor pedaço de código que pode ser logicamente isolado em um sistema, geralmente pensamos no menor pedaço de código logicamente isolado como nossas funções. Com este pequeno pedaço de código, podemos executar testes automatizados que garantem que nosso código esteja sempre gerando o resultado correto.

Por que devo testar meu código ?

Vejamos alguns bons motivos para realizar os testes de unidade.

  1. Detectar erros e problemas: testar o código ajuda a encontrar erros e falhas no software antes que ele seja entregue ao usuário final. Ao identificar e corrigir esses problemas, é possível evitar comportamentos inesperados, defeitos e problemas de segurança.
     
  2. Melhorar a qualidade do software: os testes podem ajudar a garantir que o software funcione corretamente e de acordo com as especificações. Testar o código pode ajudar a melhorar a qualidade do software e reduzir a quantidade de defeitos encontrados pelos usuários finais.
     
  3. Economizar tempo e dinheiro: testar o software pode ajudar a economizar tempo e dinheiro a longo prazo, pois os problemas são encontrados mais cedo no processo de desenvolvimento. Isso reduz a necessidade de refazer o trabalho e corrigir problemas depois que o software é entregue ao usuário final.
     
  4. Facilitar a manutenção: testes bem escritos podem ajudar a documentar o software e facilitar a manutenção, tornando mais fácil para outros desenvolvedores entender o que o código faz e como ele funciona. Isso pode ajudar a reduzir o tempo e esforço necessário para atualizar, modificar ou corrigir o software no futuro.
     
  5. Aumentar a confiança no software: testar o software ajuda a aumentar a confiança nos resultados do software, o que é particularmente importante em aplicações críticas, como sistemas médicos ou financeiros. Isso ajuda a garantir que o software esteja funcionando corretamente e possa ser usado com segurança.

Na plataforma .NET, existem várias ferramentas disponíveis para realizar testes de unidade. Aqui estão algumas das ferramentas mais populares:

  1. MSTest: MSTest é uma estrutura de teste de unidade incluída na biblioteca de classes do .NET Framework. Ele fornece uma ampla gama de recursos para criar e executar testes de unidade em aplicativos .NET.
     
  2. NUnit: NUnit é uma estrutura de teste de unidade de código aberto que permite escrever testes em C#, VB.NET ou outros idiomas compatíveis com .NET. Ele fornece muitos recursos avançados, como suporte para teoria, dados em massa e categorias de testes.
     
  3. xUnit: xUnit é outra estrutura de teste de unidade de código aberto que é semelhante ao NUnit e MSTest. Ele fornece recursos avançados, como suporte para injeção de dependência, testes em paralelo e teoria de dados.
     
  4. Teste do Azure DevOps: O Azure DevOps fornece uma ferramenta de teste de unidade integrada que pode ser usada para testar aplicativos .NET. Ele fornece recursos para criar e executar testes de unidade em ambientes locais ou em nuvem.
     
  5. Teste de carga: o Visual Studio fornece uma ferramenta de teste de carga integrada que pode ser usada para testar o desempenho de aplicativos .NET. Ele permite simular o tráfego do usuário para verificar como o aplicativo se comporta sob carga pesada.

Dentre as 3 opções de ferramentas mais comumente usadas o MSTest é maduro mas é mais lento, o NUnit também  é maduro e rápido e o xUNit é mais novo e rápido.

Vamos criar dois projetos usando o VS Code :

  • O projeto console : TesteUnidade
  • O projeto de testes usando xUnit : TesteUnidade.Test(onde vamos incluir o pacote Moq)

Recursos usados:

  • VS Code
  • .NET 7.0
  • Visual Studio Code
  • xUnit
  • Microsoft.NET.Test.Sdk

 Criando e configurando o projeto

Abra um terminal de comandos e digite o comando : dotnet new console -n TestesUnidade -f net7.0

Este comando cria um projeto console usando a versão do .NET 7.0 pois no meu ambiente eu já baixei o NET 8.0 que esta em preview.

Com isso criamos um projeto console na pasta TestesUnidade no sistema local.


Estes pacotes NuGet são usados no ecossistema .NET para habilitar a execução e relatórios de testes de unidade.

  1. Microsoft.NET.Test.Sdk: O pacote Microsoft.NET.Test.Sdk é um pacote que adiciona recursos e suporte para testes de unidade no .NET Framework. Ele é usado para habilitar a execução de testes de unidade em projetos .NET Core e .NET Standard, e também inclui suporte para a saída de resultados do teste em vários formatos, como o formato de arquivo de resultado do teste do Visual Studio.
     
  2. xUnit: O pacote xUnit é uma das estruturas de teste de unidade mais populares disponíveis para .NET. Ele fornece uma ampla gama de recursos para escrever, executar e gerenciar testes de unidade, incluindo suporte para testes assíncronos, teoria de dados e categorias de teste.
     
  3. xunit.runner.visualstudio: O pacote xunit.runner.visualstudio é uma extensão do xUnit que permite executar testes de unidade diretamente do Visual Studio. Ele fornece uma interface amigável para executar testes de unidade dentro do Visual Studio e integrar com os recursos de depuração do Visual Studio.
     
  4. coverlet.collector: O pacote coverlet.collector é um coletor de cobertura de código de teste para .NET Core e .NET Framework. Ele é usado para medir a cobertura do código em testes de unidade e fornecer relatórios de cobertura. O coverlet.collector é um pacote leve e fácil de usar que fornece resultados precisos de cobertura de código.

Agora entre no projeto criado e crie a classe Usuario.cs com o código abaixo:

namespace TesteUnidade
{
    public record Usuario(string Nome, string Sobrenome)
    {
        public int Id { get; init; }
        public DateTime DataCriacao { get; init; } = DateTime.UtcNow;
        public string Telefone { get; set; } = "+55 ";
        public bool EmailVerificado { get; set; } = false;
    }
    public class GerenciaUsuario    
    {
        private readonly List<Usuario> _usuarios = new ();
        private int contador = 1;
        public IEnumerable<Usuario> TodosUsuarios => _usuarios;
 
        public void AdicionaUsuario(Usuario usuario)
        {
            _usuarios.Add(usuario with {Id = contador++});
        }
        public void AtualizaTelefone(Usuario usuario)
        {
            var dbUsuario = _usuarios.First(x => x.Id == usuario.Id);
            dbUsuario.Telefone = usuario.Telefone;
        }
        public void VerificarEmail(int id)
        {
            var dbUsuario = _usuarios.First(x => x.Id == id);
            dbUsuario.EmailVerificado = true;
        }
    }
}

Entendendo o código usado:

Iniciamos criando a o record Usuario onde a palavra-chave "record" introduzida no C# 9 permite definir classes de dados imutáveis de maneira mais concisa e elegante. Nesse caso, a classe "Usuario" tem dois campos, "Nome" e "Sobrenome", que são definidos no construtor da classe.

A seguir, definimo na classe "Usuario" quatro propriedades: "Id", "DataCriacao", "Telefone" e "EmailVerificado".

A propriedade "Id" é uma propriedade auto-implementada que permite obter e inicializar o valor do identificador do usuário. As propriedades "DataCriacao", "Telefone" e "EmailVerificado" são propriedades de inicialização que permitem definir valores padrão para essas propriedades no momento da criação do objeto.

A propriedade "DataCriacao" é inicializada com a hora atual em UTC usando a classe "DateTime" do .NET. A propriedade "Telefone" é inicializada com a string "+55 ", representando o código do país, seguido de um espaço em branco. A propriedade "EmailVerificado" é inicializada como "false", indicando que o e-mail do usuário ainda não foi verificado.

Finalmente, as propriedades "Telefone" e "EmailVerificado" são propriedades auto-implementadas que permitem obter e definir os valores dessas propriedades. No entanto, a propriedade "Telefone" só permite definir um valor para a propriedade, enquanto a propriedade "EmailVerificado" permite definir e obter o valor da propriedade.

A seguir criamos a classe GerenciaUsuario que utiliza o record Usuario e que é responsável por gerenciar uma lista de usuários. Essa classe possui três métodos principais: "AdicionaUsuario", "AtualizaTelefone" e "VerificarEmail", que permitem adicionar, atualizar e verificar o email de um usuário na lista.

A classe utiliza o record "Usuario" que foi definido anteriormente para representar os dados dos usuários. O campo "_usuarios" é uma lista privada de usuários, que é inicializada na declaração da classe usando a sintaxe de inicialização de objeto.

O método "AdicionaUsuario" adiciona um novo usuário à lista, atribuindo um ID único ao usuário. O campo "contador" é utilizado para manter o controle dos IDs dos usuários, e é incrementado a cada vez que um novo usuário é adicionado.

O método "AtualizaTelefone" atualiza o número de telefone de um usuário existente na lista. Ele busca o usuário correspondente na lista utilizando o ID do usuário, e atualiza o número de telefone do usuário encontrado com o número de telefone do usuário passado como parâmetro.

O método "VerificarEmail" marca um usuário existente na lista como tendo seu email verificado. Ele busca o usuário correspondente na lista utilizando o ID do usuário e, em seguida, define a propriedade "EmailVerificado" do usuário encontrado como verdadeiro.

Esses métodos são exemplos simples de como podemos utilizar records em C# para representar dados e realizar operações comuns de CRUD (create, read, update, delete) em coleções de objetos. A utilização de records torna o código mais legível e menos propenso a erros, já que a sintaxe concisa e declarativa dos registros permite que nos concentremos nas operações importantes que estamos realizando, em vez de nos detalhes de implementação.

Criando o projeto de testes de unidade

Vamos criar agora o projeto de testes . Para isso , vamos digitar no terminal de comandos:
dotnet new xunit -n TesteUnidade.Test -f net7.0

dot

Uma vez que o projeto foi criado com sucesso, precisamos adicionar uma referência neste projeto ao nosso projeto inicial TestesUnidade digitando o comando:

dotnet add TesteUnidade.Test/TesteUnidade.Test.csproj reference TesteUnidade\TesteUnidade.csproj

Neste momento o arquivo de projeto TesteUnidade.Test.csproj deverá ter o seguinte código:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
    <IsTestProject>true</IsTestProject>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
    <PackageReference Include="xunit" Version="2.4.2" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="coverlet.collector" Version="3.2.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\TesteUnidade\TesteUnidade.csproj" />
  </ItemGroup>
</Project>

Observe as referências aos pacotes xunit e a projeto TesteUnidade.csproj.

Agora vamos criar a classe UsuarioTests no projeto de testes definindo o seguinte código:

namespace TesteUnidade.Test;
public class UsuarioTests
{
    [Fact]
    public void Add_CriaNovoUsuario()
    {
        // Arrange
        var gerenciaUsuario = new GerenciaUsuario();
        // Act
        gerenciaUsuario.AdicionaUsuario(new(
                "José Carlos", "Macoratti"
        ));
        // Assert
        var usuarioSalvo = Assert.Single(gerenciaUsuario.TodosUsuarios);
        Assert.NotNull(usuarioSalvo);
        Assert.Equal("José Carlos", usuarioSalvo.Nome);
        Assert.Equal("Macoratti", usuarioSalvo.Sobrenome);
        Assert.NotEmpty(usuarioSalvo.Telefone);
        Assert.False(usuarioSalvo.EmailVerificado);
    }
    [Fact]
    public void Verify_VerificaEmailAddress()
    {
        // Arrange
        var gerenciaUsuario = new GerenciaUsuario();
        // Act
        gerenciaUsuario.AdicionaUsuario(new(
                "José Carlos", "Macoratti"
        ));
        var primeiroUsuario = gerenciaUsuario.TodosUsuarios.ToList().First();
        gerenciaUsuario.VerificarEmail(primeiroUsuario.Id);
        // Assert
        var usuarioSalvo = Assert.Single(gerenciaUsuario.TodosUsuarios);
        Assert.True(usuarioSalvo.EmailVerificado);
    }
    [Fact]
    public void Update_UpdateTelefoneNumber()
    {
        // Arrange
        var gerenciaUsuario = new GerenciaUsuario();
        // Act
        gerenciaUsuario.AdicionaUsuario(new(
                "José Carlos", "Macoratti"
        ));
        var primeiroUsuario = gerenciaUsuario.TodosUsuarios.ToList().First();
        primeiroUsuario.Telefone = "+5501070808080";
        gerenciaUsuario.AtualizaTelefone(primeiroUsuario);
        // Assert
        var usuarioSalvo = Assert.Single(gerenciaUsuario.TodosUsuarios);
        Assert.Equal("+5501070808080", usuarioSalvo.Telefone);
    }
}

Cada um dos métodos de teste é marcado com o atributo [Fact], indicando que é um método de teste que deve ser executado pela estrutura de testes.

Vamos entender o código :

  1. O método "Add_CriaNovoUsuario" testa o método "AdicionaUsuario" da classe "GerenciaUsuario". Ele cria uma instância da classe "GerenciaUsuario", adiciona um novo usuário à lista, e depois testa se o usuário foi adicionado corretamente, verificando se os valores dos campos do usuário correspondem aos valores esperados.
  2. O método "Verify_VerificaEmailAddress" testa o método "VerificarEmail" da classe "GerenciaUsuario". Ele cria uma instância da classe "GerenciaUsuario", adiciona um novo usuário à lista, verifica o primeiro usuário na lista e, em seguida, marca seu e-mail como verificado. O método de teste verifica se o e-mail do usuário foi marcado como verificado.
  3. O método "Update_UpdateTelefoneNumber" testa o método "AtualizaTelefone" da classe "GerenciaUsuario". Ele cria uma instância da classe "GerenciaUsuario", adiciona um novo usuário à lista, verifica o primeiro usuário na lista, atualiza o número de telefone desse usuário e, em seguida, verifica se o número de telefone do usuário foi atualizado corretamente.

Esses métodos de teste são importantes porque ajudam a garantir que os métodos da classe "GerenciaUsuario" estejam funcionando corretamente e sem erros. Eles também ajudam a documentar o comportamento esperado dos métodos da classe, tornando mais fácil para outros desenvolvedores entenderem o que a classe faz e como ela deve ser usada.

Este seria um exemplo básico do uso do xUnit para criar testes de unidade na plataforma .NET.

Estando no projeto de testes vamos executar o comando : dotnet test

O comando "dotnet test" é usado para compilar, executar e relatar os resultados dos testes de unidade para um projeto específico.

Ao executar o comando "dotnet test", o SDK do .NET compila o projeto de teste e os projetos de destino em modo de depuração e, em seguida, executa os testes de unidade em paralelo em um processo separado.

O resultado dos testes é exibido no console, mostrando se cada teste passou ou falhou e, em caso de falha, quais foram as informações adicionais sobre o erro. Além disso, um arquivo de relatório de teste pode ser gerado para facilitar a análise dos resultados dos testes.

Usando o MOQ

Agora vamos para a próxima etapa e verificar como podemos utilizar MOQ.

Mas o que é Moq e o que significa Mocking?

Se é a primeira vez que você ouve os termos "mock", "objetos mock", "mocar" e "mocking"  você vai estranhar, e,  isso é normal, afinal essa palavra ainda não foi incorporada ao seu vocabulário. A partir de hoje você aprendeu mais uma palavra.

 

Existe na língua portuguesa o verbo mocar que significa enganar, atraiçoar ou ainda esconder (popular), mas na área de software mocar objetos ou Mock Objects significa objetos que imitam objetos reais para realização de testes de software.

 

Na programação orientada a objeto, objetos mock ou fictícios são objetos simulados que imitam o comportamento de objetos reais. Os objetos Mocks são geralmente usados em testes de unidade.

 

Assim, os objetos Mock são criados para testar o comportamento de algum outro objeto(real); com isso estamos mocando, ou seja, simulando ou fingindo o objeto real e fazendo algumas operações de forma controlada de modo que o resultado retornado (teste) é sempre válido.

Desta forma quando um serviço depende de outro serviço e queremos testar esse serviço, em vez de fazer o processo de inicialização completo do segundo serviço, podemos mocar o serviço (fingindo que ele está totalmente funcional) e podemos executar nossos testes com base nisso.

Para mostrar isso vamos criar uma nova classe no projeto TesteUnidade chamada CarrinhoCompra incluindo o código a seguir:

namespace TesteUnidade;
public record Produto(int Id, string Nome, double Preco);
public interface IDatabaseService
{
    bool SalvarItemCarrinhoCompra(Produto produto);
    bool RemoverItemCarrinhoCompra(int? id);
}
// CarrinhoCompra
public class CarrinhoCompra
{
    private IDatabaseService _dbService;
    public CarrinhoCompra(IDatabaseService dbService)
    {
        _dbService = dbService;
    }
    public bool AddProduto(Produto? produto)
    {
        if (produto == null)
            return false;
        if (produto.Id == 0)
            return false;
        _dbService.SalvarItemCarrinhoCompra(produto);
        return true;
    }
    public bool DeleteProduto(int? id)
    {
        if (id == null)
            return false;
 
        if (id == 0)
            return false;
 
        _dbService.RemoverItemCarrinhoCompra(id);
        return true;
    }
}

O código acima apresenta uma implementação de uma classe CarrinhoCompra que tem como objetivo adicionar e remover produtos de um carrinho de compras. A classe recebe uma instância de IDatabaseService via injeção de dependência através do construtor.

A interface IDatabaseService possui dois métodos: SalvarItemCarrinhoCompra e RemoverItemCarrinhoCompra, que são responsáveis por salvar e remover um item do carrinho no banco de dados, respectivamente.

A classe Produto é um record que representa um produto com um Id, um Nome e um Preco.

O método AddProduto da classe CarrinhoCompra adiciona um produto no carrinho, verificando se o objeto passado não é nulo e se o Id do produto é diferente de zero. Caso positivo, chama o método SalvarItemCarrinhoCompra da instância de IDatabaseService injetada via construtor e retorna true. Caso contrário, retorna false.

O método DeleteProduto da classe CarrinhoCompra remove um produto do carrinho, verificando se o id passado não é nulo e se é diferente de zero. Caso positivo, chama o método RemoverItemCarrinhoCompra da instância de IDatabaseService injetada via construtor e retorna true. Caso contrário, retorna false.

Criando testes de unidade usando o MOQ

Agora vamos começar a criar testes de unidade para essas funcionalidades definidas na classe CarrinhoCompra em nosso projeto de testes.

Como você pode ver, esta funcionalidade depende de um serviço database, o que significa que precisamos simular esse serviço para que possamos testar as implementações que escrevemos.

Vamos iniciar instalando o pacote Moq no projeto de testes : dotnet add package Moq

Agora vamos criar uma nova classe dentro do nosso projeto de testes TesteUnidade.Test chamada CarrinhoCompraTest usando o seguinte código :

using Moq;
namespace TesteUnidade.Test;
public class CarrinhoCompraTest
{
    public readonly Mock<IDatabaseService> _dbServiceMock = new();
    [Fact]
    public void AddProduto_Success()
    {
        var product = new Produto(1, "Caderno", 5);
        _dbServiceMock.Setup(x => x.SalvarItemCarrinhoCompra(product)).Returns(true);
        // Arrange
        CarrinhoCompra carrinhoCompra = new (_dbServiceMock.Object);
        // Act
        var result = carrinhoCompra.AddProduto(product);
        // Assert
        Assert.True(result);
        _dbServiceMock.Verify(x => x.SalvarItemCarrinhoCompra(It.IsAny<Produto>()), Times.Once);
    }
    [Fact]
    public void AddProduto_Failure_InvalidPayload()
    {
        // Arrange
        CarrinhoCompra carrinhoCompra = new (_dbServiceMock.Object);
        // Act
        var result = carrinhoCompra.AddProduto(null);
        // Assert
        Assert.False(result);
        _dbServiceMock.Verify(x => x.SalvarItemCarrinhoCompra(It.IsAny<Produto>()), Times.Never);
    }
    [Fact]
    public void RemoveProduto_Success()
    {
        var product = new Produto(1, "Caderno", 5);
        _dbServiceMock.Setup(x => x.RemoverItemCarrinhoCompra(product.Id)).Returns(true);
        // Arrange
        CarrinhoCompra carrinhoCompra = new (_dbServiceMock.Object);
        // Act
        var result = carrinhoCompra.DeleteProduto(product.Id);
        // Assert
        Assert.True(result);
        _dbServiceMock.Verify(x => x.RemoverItemCarrinhoCompra(It.IsAny<int>()), Times.Once);
    }
    [Fact]
    public void RemoveProduto_Failed()
    {
        _dbServiceMock.Setup(x => x.RemoverItemCarrinhoCompra(null)).Returns(false);
        // Arrange
        CarrinhoCompra carrinhoCompra = new (_dbServiceMock.Object);
        // Act
        var result = carrinhoCompra.DeleteProduto(null);
        // Assert
        Assert.False(result);
        _dbServiceMock.Verify(x => x.RemoverItemCarrinhoCompra(null), Times.Never);
    }
    [Fact]
    public void RemoveProduto_Failed_InvalidId()
    {
        _dbServiceMock.Setup(x => x.RemoverItemCarrinhoCompra(null)).Returns(false);
        // Arrange
        CarrinhoCompra carrinhoCompra = new (_dbServiceMock.Object);
        // Act
        var result = carrinhoCompra.DeleteProduto(0);
        // Assert
        Assert.False(result);
        _dbServiceMock.Verify(x => x.RemoverItemCarrinhoCompra(null), Times.Never);
    }
}

O objetivo do teste é garantir que os métodos AddProduto e RemoveProduto da classe CarrinhoCompra funcionem corretamente.

Para isso, são definidos quatro testes de unidade, cada um usando um cenário diferente, que são os seguintes:

  1. AddProduto_Success(): Testa se o método AddProduto da classe CarrinhoCompra adiciona com sucesso um produto válido ao carrinho de compras e salva o item no banco de dados usando o método SalvarItemCarrinhoCompra do serviço de banco de dados.

    Para isso, um objeto de produto válido é criado e simulado o comportamento do método SalvarItemCarrinhoCompra do serviço de banco de dados usando o MOQ.

    A seguir, é criada uma instância da classe CarrinhoCompra, o método AddProduto é chamado passando o objeto de produto criado e o resultado é verificado usando o método Assert.True.

    Por fim, o MOQ verifica se o método SalvarItemCarrinhoCompra do serviço de banco de dados foi chamado uma vez.
     
  2. AddProduto_Failure_InvalidPayload(): Testa se o método AddProduto da classe CarrinhoCompra retorna falso quando um objeto de produto inválido é passado como parâmetro e verifica que o método SalvarItemCarrinhoCompra do serviço de banco de dados não foi chamado.

    Para isso, é criada uma instância da classe CarrinhoCompra e o método AddProduto é chamado passando um objeto nulo.

    O resultado é verificado usando o método Assert.False e o MOQ verifica se o método SalvarItemCarrinhoCompra do serviço de banco de dados nunca foi chamado.
     
  3. RemoveProduto_Success(): Testa se o método RemoveProduto da classe CarrinhoCompra remove com sucesso um produto válido do carrinho de compras e remove o item correspondente do banco de dados usando o método RemoverItemCarrinhoCompra do serviço de banco de dados.

    Com esse objetivo, um objeto de produto válido é criado e simulado o comportamento do método RemoverItemCarrinhoCompra do serviço de banco de dados usando o MOQ.

    Em seguida, é criada uma instância da classe CarrinhoCompra, o método RemoveProduto é chamado passando o ID do produto criado e o resultado é verificado usando o método Assert.True.

    Por fim, o MOQ verifica se o método RemoverItemCarrinhoCompra do serviço de banco de dados foi chamado uma vez.
     
  4. RemoveProduto_Failed() e RemoveProduto_Failed_InvalidId(): Testam se o método RemoveProduto da classe CarrinhoCompra retorna falso quando um ID de produto inválido é passado como parâmetro ou quando o ID é nulo.

    Desta forma, o MOQ é configurado para retornar falso quando o método RemoverItemCarrinhoCompra é chamado com um ID inválido ou nulo.

    A seguir, são criadas instâncias da classe CarrinhoCompra e o método RemoveProduto é chamado passando um ID inválido ou nulo em cada um dos testes.

    O resultado é verificado usando o método Assert.False e o MOQ verifica se o método RemoverItemCarrinhoCompra do serviço de banco de dados nunca foi chamado com um ID inválido ou nulo.

Executando o teste usando o comando : dotnet test   teremos o resultado abaixo:

Nos exemplos apresentados, são utilizados os seguintes recursos do pacote MOQ:

  • Mock<T>: Classe usada para criar um objeto simulado de uma interface ou classe abstrata. Nesse caso, mock é uma instância da classe Mock<IFuncionarioService> e é usada para criar um objeto simulado do serviço de funcionário.
  • Setup: Método usado para configurar o comportamento do objeto simulado. O método Setup é usado para definir o comportamento do método que está sendo testado. Por exemplo, em mock.Setup(p => p.AddFuncionario(funci)).ReturnsAsync(funci);, o comportamento do método AddFuncionario é configurado para retornar um objeto Funcionario que é passado como argumento.
  • ReturnsAsync: Método usado para configurar o valor de retorno do método que está sendo testado. Esse método é usado para especificar que o método deve retornar um valor assíncrono.
  • Verify: método usado para verificar se um método do objeto simulado foi chamado com os argumentos corretos. Nesse caso, mock.Verify(p => p.AddFuncionario(funci), Times.Once); é usado para verificar se o método AddFuncionario do objeto simulado foi chamado uma vez com o objeto Funcionario correto.
  • It.IsAny<T>(): Método usado para especificar que um argumento pode ser qualquer valor do tipo T. Esse método é usado quando não é necessário verificar um valor específico do argumento.
  • Times: Enumeração usada para especificar quantas vezes um método do objeto simulado deve ser chamado. Nesse caso, Times.Once é usado para especificar que o método deve ser chamado uma vez.
  • Callback: Método usado para executar uma ação quando o método que está sendo testado é chamado. Nesse caso, mock.Setup(p => p.DeleteFuncionario(id)).Callback(() => deleted = true); é usado para definir a variável deleted como true quando o método DeleteFuncionario é chamado.
  • ThrowsAsync: Método usado para simular uma exceção sendo lançada pelo método que está sendo testado. Esse método é usado para verificar se o código de tratamento de exceção está funcionando corretamente. Por exemplo, em mock.Setup(p => p.GetFuncionarioId(0)).ThrowsAsync(new Exception());, o comportamento do método GetFuncionarioId é configurado para lançar uma exceção quando o argumento for 0.

Pegue os projetos aqui:   TesteUnidadeMOQ.zip (sem as referências)

"Esta é uma palavra fiel, e digna de toda a aceitação, que Cristo Jesus veio ao mundo, para salvar os pecadores, dos quais eu sou o principal."
1 Timóteo 1:15

Referências:


José Carlos Macoratti