Neste artigo vou ensinar sobre escrita de testes unitários em .NET 5, de código utilizando padrão CQRS, utilizando as seguintes ferramentas/bibliotecas:
- xUnit
- Moq
- AutoFixture
- Shouldly
Vai ter uma abordagem mais prática que teórica, mas vou me esforçar para cobrir os conceitos necessários para o entendimento!
Código-fonte: https://github.com/luisdeol/xunit-moq-autofixture-shoudly-article
Para que Testes Unitários?
Testes Unitários se referem à escrita de código para testar unidades de funcionalidade em seu código.
Entre os principais benefícios, estão:
- Melhorar o desenho do código, por ser fácil detectar quando um código está fortemente acoplado a implementações, e consequentemente diminuindo a sua testabilidade e forçando sua melhoria.
- Cobertura de caminhos principais, alternativos e de exceção;
- Detecção de regressão de erros;
- Documentação de regra de negócio;
Eu falo melhor sobre esses pontos e os desafios nesse artigo, que é parte de uma série com 4 artigos. Nessa série você vai aprender também sobre xUnit, padrões de escrita de testes unitários, Moq e AutoFixture. Por conta disso, não vou detalhar sobre essas bibliotecas aqui.
O que vai ser testado
O sistema fictício é de gestão de alunos de uma escola, e o componente a ser testado é o Command AddStudentHandler, seguindo o padrão CQRS. Ele tem duas dependências: o IStudentRepository e IErpIntegrationService. Para ter um cenário de maior utilização das bibliotecas Moq e Shoudly, o método Handle do AddStudentHandler vai retornar um objeto AddStudentViewModel, permitindo checagens de suas propriedades em comparação aos dados de entrada.
Logo abaixo coloco o código do AddCommand e AddCommandHandler.
Código AddStudent
public class AddStudent : IRequest<AddStudentViewModel>
{
public string FullName { get; set; }
public string Document { get; set; }
public string Class { get; set; }
public DateTime BirthDate { get; set; }
public Student ToEntity()
=> new Student(FullName, Document, Class, BirthDate);
}
Código AddStudentHandler
public class AddStudentHandler : IRequestHandler<AddStudent, AddStudentViewModel>
{
private readonly IStudentRepository _studentRepository;
private readonly IErpIntegrationService _erpIntegrationService;
public AddStudentHandler(IStudentRepository studentRepository, IErpIntegrationService erpIntegrationService)
{
_studentRepository = studentRepository;
_erpIntegrationService = erpIntegrationService;
}
public async Task<AddStudentViewModel> Handle(AddStudent request, CancellationToken cancellationToken)
{
var student = request.ToEntity();
await _studentRepository.AddAsync(student);
var erpStudent = ErpStudent.FromEntity(student);
await _erpIntegrationService.SyncStudent(erpStudent);
return AddStudentViewModel.FromEntity(student);
}
}
A idéia então é validar algumas coisas, como:
- Métodos do IStudentRepository e IErpIntegrationService foram chamados uma vez;
- Objeto retornado tem informações consistentes dos dados do parâmetro AddCommand.
Para verificar os outros componentes, indico olhar no código-fonte, já que colocar tudo aqui deixaria “poluído”.
Escrevendo o teste unitário
Primeiramente, a ferramenta utilizada para criação e execução de testes unitários é o xUnit. É uma ferramenta bem popular para isso, e com outras alternativas com o NUnit e MSTest (este último não é tão comum de ser utilizado).
O primeiro passo é criar a classe AddStudentTests, que é onde ficará os métodos de testes. Para marcar um método como teste unitário do xUnit, basta adicionar o atributo [Fact].
O padrão de nomenclatura utilizado é o Given_When_Then, e o padrão de estrutura é o Arrange, Act, Assert.
public class AddStudentTests
{
[Fact]
public async Task ValidStudent_HandlerIsCalled_ReturnValidStudentViewModel() {
// Arrange
// Act
// Assert
}
}
Em seguida, é estruturada a parte Arrange, onde as dependências são definidas e seu comportamento alterado através de Mocks. As seguintes chamadas de dependências precisam ser “mockadas” (isto é, ter seu comportamento alterado, seja com retorno ou não):
- IStudentRepository.AddAsync: aqui não é necessário definir nada mais além do Mock básico, sem alteração explícita, por não ter tipo de retorno;
- IErpIntegrationService.SyncStudent: neste caso, é definido um retorno de sucesso para este método;
Além disso, ao invés de instanciar manualmente um objeto de tipo AddStudent (que é parâmetro do método Handle do AddStudentHandler), é utilizada a biblioteca AutoFixture para gerar um objeto rapidamente.
Os mocks são então passados para uma instância do AddStudentHandler, através de sua propriedade Object, resultando no código abaixo.
public class AddStudentTests
{
[Fact]
public async Task ValidStudent_HandlerIsCalled_ReturnValidStudentViewModel() {
// Arrange
var addStudent = new Fixture().Create<AddStudent>();
var studentRepositoryMock = new Mock<IStudentRepository>();
var erpIntegrationService = new Mock<IErpIntegrationService>();
erpIntegrationService.Setup(e => e.SyncStudent(It.IsAny<ErpStudent>())).Returns(Task.FromResult(true));
var addStudentHandler = new AddStudentHandler(studentRepositoryMock.Object, erpIntegrationService.Object);
// Act
// Assert
}
}
Terminada essa parte, é feito então o Act, que consiste da chamada do método AddStudentHandler.Handle, passando o objeto de tipo AddStudent criado com o AutoFixture e um CancellationToken.
[Fact]
public async Task ValidStudent_HandlerIsCalled_ReturnValidStudentViewModel() {
// Arrange
var addStudent = new Fixture().Create<AddStudent>();
var studentRepositoryMock = new Mock<IStudentRepository>();
var erpIntegrationService = new Mock<IErpIntegrationService>();
erpIntegrationService.Setup(e => e.SyncStudent(It.IsAny<ErpStudent>())).Returns(Task.FromResult(true));
var addStudentHandler = new AddStudentHandler(studentRepositoryMock.Object, erpIntegrationService.Object);
// Act
var result = await addStudentHandler.Handle(addStudent, new CancellationToken());
// Assert
}
Finalmente, na parte de Assert, é feita a verificação comentada anteriormente utilizando o Shouldly, que oferece uma sintaxe mais fluída de checagem do que a classe Assert utilizando métodos como o ShouldBe, além do Moq para as checagens das chamadas dos métodos dos mocks.
public class AddStudentTests
{
[Fact]
public async Task ValidStudent_HandlerIsCalled_ReturnValidStudentViewModel() {
// Arrange
var addStudent = new Fixture().Create<AddStudent>();
var studentRepositoryMock = new Mock<IStudentRepository>();
var erpIntegrationService = new Mock<IErpIntegrationService>();
erpIntegrationService.Setup(e => e.SyncStudent(It.IsAny<ErpStudent>())).Returns(Task.FromResult(true));
var addStudentHandler = new AddStudentHandler(studentRepositoryMock.Object, erpIntegrationService.Object);
// Act
var result = await addStudentHandler.Handle(addStudent, new CancellationToken());
// Assert
result.FullName.ShouldBe(addStudent.FullName);
result.Document.ShouldBe(addStudent.Document);
result.Class.ShouldBe(addStudent.Class);
result.BirthDate.ShouldBe(addStudent.BirthDate);
studentRepositoryMock.Verify(s => s.AddAsync(It.IsAny<Student>()), Times.Once);
erpIntegrationService.Verify(s => s.SyncStudent(It.IsAny<ErpStudent>()), Times.Once);
}
}
E com isso terminamos a escrita do teste unitário focado no AddStudentHandler! Claro que em um cenário mais real se testariam mais cenários, mas a intenção foi mostrar a estrutura de um teste unitário utilizando as tecnologias xUnit, Moq, AutoFixture e Shoudly.
Quer alavancar sua carreira como Desenvolvedor(a) .NET?
Opa, aqui é o Luis Felipe (LuisDev), criador do blog LuisDev.
Além de Desenvolvedor .NET Sênior, eu sou instrutor de mais de 700 alunos e também tenho dezenas de mentorados.
Conheça o com mais de 800 video-aulas sobre C# e desenvolvimento de APIs com ASP NET Core, Microsserviços com ASP NET Core, Arquitetura de Software, Computação em Nuvem, SQL, HTML, CSS e JavaScript, JavaScript Intermediário, TypeScript, Desenvolvimento Front-End com Angular, e Desenvolvimento Front-end com React. Diversos mini-cursos disponíveis aos alunos e atualizações gratuitas.
Suporte dedicado, e comunidade de centenas de alunos.
Completo e online, destinado a profissionais que querem dar seu próximo passo em sua carreira como desenvolvedores .NET.
Clique aqui para ter mais informações e garantir sua vaga
Espero que o artigo tenha sido útil. Até o próximo artigo!