Para entender os conceitos utilizados neste código em relação a testes unitários, precisamos focar em alguns princípios chave e práticas que facilitam a testabilidade do código. Vou abordar os conceitos principais utilizados neste código e como eles se relacionam com testes unitários.
-
Injeção de Dependência (Dependency Injection):
- Definição: É um padrão de design que permite a injeção de dependências (objetos que uma classe precisa para funcionar) em uma classe através do seu construtor ou métodos.
- No código: O repositório
IEmployeeRepository
é injetado no construtor da classeEmployeeService
.
public EmployeeService(IEmployeeRepository repository) { _repository = repository; }
- Para testes unitários: A injeção de dependência facilita a substituição de dependências reais por mocks ou stubs durante os testes, permitindo isolar a unidade de código que está sendo testada.
-
Interface:
- Definição: Interfaces são contratos que definem quais métodos e propriedades uma classe deve implementar, sem fornecer a implementação real.
- No código:
IEmployeeRepository
é uma interface que define o métodoAdd
.
public interface IEmployeeRepository { void Add(Employee employee); }
- Para testes unitários: O uso de interfaces permite a criação de mocks. Você pode criar uma implementação falsa (
mock
) deIEmployeeRepository
para testar o comportamento daEmployeeService
sem interagir com a camada de dados real.
-
Mocking:
- Definição: É o processo de criação de objetos falsos que imitam o comportamento de objetos reais. Mocks são usados para testar a interação entre unidades de código.
- Para testes unitários: Em testes, você pode usar bibliotecas de mocking (como Moq em C#) para criar um mock de
IEmployeeRepository
e verificar se os métodos corretos foram chamados.
Aqui está um exemplo de como você poderia escrever um teste unitário para o método Add
da EmployeeService
usando a biblioteca Moq:
using Moq;
using Xunit;
using HrManager.Application.Models.InputModels;
using HrManager.Application.Models.ViewModels;
using HrManager.Application.Services;
using HrManager.Core.Repositories;
public class EmployeeServiceTests
{
[Fact]
public void Add_Should_Call_Repository_Add_And_Return_ViewModel()
{
// Arrange
var mockRepository = new Mock<IEmployeeRepository>();
var service = new EmployeeService(mockRepository.Object);
var inputModel = new AddEmployeeInputModel
{
// Inicialize o modelo de entrada com dados de teste
};
var expectedEmployee = inputModel.ToEntity();
// Act
var result = service.Add(inputModel);
// Assert
mockRepository.Verify(r => r.Add(It.Is<Employee>(e => e == expectedEmployee)), Times.Once);
Assert.NotNull(result);
Assert.IsType<AddEmployeeViewModel>(result);
Assert.Equal(expectedEmployee.Name, result.Name); // Exemplo de verificação de propriedade
}
}
-
Arrange: Configuramos o cenário do teste.
- Criamos um mock de
IEmployeeRepository
usando Moq. - Criamos uma instância do
EmployeeService
passando o mock do repositório. - Inicializamos um
AddEmployeeInputModel
com dados de teste. - Convertemos o modelo de entrada em uma entidade esperada.
- Criamos um mock de
-
Act: Executamos o método
Add
doEmployeeService
. -
Assert: Verificamos se o método
Add
do repositório foi chamado uma vez com a entidade esperada.- Usamos
mockRepository.Verify
para verificar a interação com o mock. - Checamos se o resultado não é nulo e se é do tipo correto.
- Verificamos propriedades específicas no resultado para garantir que os dados foram processados corretamente.
- Usamos
O código utiliza injeção de dependência e interfaces para promover a separação de preocupações e facilitar o teste. Isso permite a criação de testes unitários eficazes, onde dependências externas podem ser mockadas para isolar a unidade de código que está sendo testada. O uso de bibliotecas de mocking como Moq facilita a verificação de interações e comportamento do código em testes.
- XUnit
- Moq (Mock)
- Auto fixture
- Shouldly
Esse código é um exemplo de testes unitários usando a biblioteca xUnit.net, Moq para criação de mocks, AutoFixture para geração de dados aleatórios e Shouldly para assertivas de teste mais expressivas. Vamos analisar cada parte do código:
using AutoFixture;
using HrManager.Application.Models.InputModels;
using HrManager.Application.Services;
using HrManager.Core.Entities;
using HrManager.Core.Exceptions;
using HrManager.Core.Repositories;
using Moq;
using Shouldly;
using System;
using Xunit;
namespace HrManager.UnitTests.Application.Services.EmployeeServiceTests
{
public class EmployeeServiceAddTests
{
[Fact]
public void ValidEmployee_AddIsCalled_ReturnValidEmployeeViewModel()
{
// Arrange
var addEmploymentInputModel = new Fixture().Create<AddEmployeeInputModel>();
var employeeRepositoryMock = new Mock<IEmployeeRepository>();
var employeeService = new EmployeeService(employeeRepositoryMock.Object);
// Act
var result = employeeService.Add(addEmploymentInputModel);
// Assert
Assert.Equal(addEmploymentInputModel.Role, result.Role);
Assert.Equal(addEmploymentInputModel.FullName, result.FullName);
Assert.Equal(addEmploymentInputModel.Document, result.Document);
Assert.Equal(addEmploymentInputModel.BirthDate, result.BirthDate);
Assert.Equal(addEmploymentInputModel.RoleLevel, result.RoleLevel);
Assert.Equal(addEmploymentInputModel.Role, result.Role);
result.FullName.ShouldBe(addEmploymentInputModel.FullName);
result.Document.ShouldBe(addEmploymentInputModel.Document);
result.BirthDate.ShouldBe(addEmploymentInputModel.BirthDate);
result.RoleLevel.ShouldBe(addEmploymentInputModel.RoleLevel);
result.Role.ShouldBe(addEmploymentInputModel.Role);
employeeRepositoryMock.Verify(er => er.Add(It.IsAny<Employee>()), Times.Once);
}
[Fact]
public void InvalidBirthDateForEmployee_AddIsCalled_ThrowAnInvalidBirthDateException()
{
// Arrange
var addEmploymentInputModel = new Fixture().Create<AddEmployeeInputModel>();
addEmploymentInputModel.BirthDate = DateTime.Today.AddDays(1);
var employeeRepositoryMock = new Mock<IEmployeeRepository>();
var employeeService = new EmployeeService(employeeRepositoryMock.Object);
// Act + Assert
var exception = Assert.Throws<BirthDateCannotBeInTheFutureException>(() =>
employeeService.Add(addEmploymentInputModel));
Assert.Equal("Birth date cannot be in the future.", exception.Message);
Should.Throw<BirthDateCannotBeInTheFutureException>(() =>
employeeService.Add(addEmploymentInputModel))
.Message.ShouldBe("Birth date cannot be in the future.");
}
}
}
- Usings Iniciais: Importações de namespaces necessários para o teste, incluindo
AutoFixture
para criação de dados aleatórios, definições de modelos (AddEmployeeInputModel
), serviços (EmployeeService
), entidades (Employee
), exceções (BirthDateCannotBeInTheFutureException
) e repositórios (IEmployeeRepository
).
- Namespace
HrManager.UnitTests.Application.Services.EmployeeServiceTests
: A estrutura do namespace sugere que esses testes estão no contexto de testes para a classeEmployeeService
no projetoApplication.Services
dentro do projetoHrManager
. - Classe
EmployeeServiceAddTests
: Classe de testes para testar o métodoAdd
da classeEmployeeService
.
-
ValidEmployee_AddIsCalled_ReturnValidEmployeeViewModel
:- Arrange: Preparação para o teste. Utiliza o
AutoFixture
para criar umAddEmployeeInputModel
com dados aleatórios. - Cria um mock de
IEmployeeRepository
e o injeta noEmployeeService
. - Modifica a data de nascimento (
BirthDate
) para garantir que não seja no futuro. - Act: Chama o método
Add
doEmployeeService
com o modelo de entrada criado. - Assert: Verifica se o método
Add
do repositório foi chamado uma vez com qualquer instância deEmployee
. Compara as propriedades do resultado (result
) com as propriedades do modelo de entrada (addEmploymentInputModel
) usando assertivas do xUnit (Assert.Equal
) e Shouldly (result.FullName.ShouldBe
, etc.).
- Arrange: Preparação para o teste. Utiliza o
-
InvalidBirthDateForEmployee_AddIsCalled_ThrowAnInvalidBirthDateException
:- Arrange: Prepara um
AddEmployeeInputModel
com uma data de nascimento (BirthDate
) no futuro. - Cria um mock de
IEmployeeRepository
e o injeta noEmployeeService
. - Act + Assert: Verifica se chamar o método
Add
doEmployeeService
com um modelo de entrada inválido resulta em uma exceção do tipoBirthDateCannotBeInTheFutureException
. Usa assertivas do xUnit (Assert.Throws
) e Shouldly (Should.Throw
) para validar a exceção e sua mensagem.
- Arrange: Prepara um
- Mocking (
Mock<IEmployeeRepository>
): Usado para simular o comportamento do repositório de empregados sem realmente acessar o banco de dados. - AutoFixture: Utilizado para criar dados aleatórios, facilitando a preparação de cenários de teste.
- Assertivas:
- xUnit (
Assert.Equal
,Assert.Throws
): Assertivas padrão do xUnit para verificar resultados. - Shouldly (
ShouldBe
,Should.Throw
): Lib adicional para assertivas mais expressivas e legíveis.
- xUnit (
Este código exemplifica uma abordagem típica de teste unitário em C#, onde cada método de teste é focado em validar um cenário específico e verificar o comportamento esperado da classe EmployeeService
ao adicionar empregados.