Mocking Test no .NET Core: utilizando o framework NSubstitute
O objetivo deste artigo é descrever o uso de práticas de Mocking Test com o .NET Core, empregando para isto do framework NSubstitute.
O objetivo deste artigo é descrever o uso de práticas de Mocking Test com o .NET Core, empregando para isto do framework NSubstitute.
Introdução
Nem sempre a implementação de testes de unidade será uma tarefa fácil. Dependências entre as diferentes partes de um software, a inexistência de ambientes específicos para validação e a integração com servidores externos são alguns dos fatores que podem limitar ou, até mesmo, inviabilizar este tipo de prática.
E como superar estas dificuldades? A solução nestes casos envolve a simulação/imitação de comportamentos considerados impossíveis de se testar. Recursos conhecidos como Mocks simplificam tal processo, fornecendo meios para emular o funcionamento de um objeto e assim permitir sua validação sem grandes esforços de codificação.
Frameworks para a criação de Mock Objects dispensam a implementação de estruturas de código que seriam descartadas posteriormente. Isto acontece através de mecanismos que permitem definir o retorno de métodos, propriedades e até o lançamento de exceções dentro de classes contendo testes de unidade. Duas alternativas gratuitas para mocking na plataforma .NET são os frameworks Moq e NSubstitute.
A finalidade deste artigo é demonstrar a criação de Mock Objects em testes de unidade gerados com o .NET Core, utilizando para tanto o framework NSubstitute.
Criando o projeto a ser testado
Para implementar os projetos apresentados nesta e na próxima seção foram usados os seguintes recursos:
- O Microsoft Visual Studio Community 2017;
- O .NET Core 1.1;
- A versão 2.2.0 do xUnit, framework para implementação de testes de unidade em .NET;
- O NSubstitute 2.0.2.
O exemplo aqui descrito é uma variação de um projeto baseado no framework Moq, o qual foi detalhado em outro artigo que escrevi anteriormente.
Inicialmente será criado um projeto do tipo Class Library (.NET Core) chamado ConsultaCredito, como indicado na imagem a seguir:
A biblioteca ConsultaCredito conterá os recursos empregados na interação com um serviço de consulta a crédito, utilizando para isto o número de CPF de eventuais clientes durante a realização de pesquisas. No próximo diagrama estão as estruturas a serem implementadas nesse projeto:
Na listagem a seguir, é possível observar:
- O enumeration StatusConsultaCredito, com os status esperados na interação com o serviço de consulta a crédito;
- A classe Pendencia, com propriedades que identificam a pessoa física e suas eventuais pendências financeiras.
using System;
namespace ConsultaCredito
{
public enum StatusConsultaCredito
{
ParametroEnvioInvalido = -2,
ErroComunicacao = -1,
SemPendencias = 0,
Inadimplente = 1
}
public class Pendencia
{
public string CPF { get; set; }
public string NomePessoa { get; set; }
public string NomeReclamante { get; set; }
public string DescricaoPendencia { get; set; }
public DateTime DataPendencia { get; set; }
public double VlPendencia { get; set; }
}
}
Dentre as situações previstas pelo enumeration StatusConsultaCredito estão:
- O envio de um CPF inválido (valor ParametroEnvioInvalido);
- O lançamento de uma exceção ao invocar o serviço de consulta a crédito (valor ErroComunicacao);
- A inexistência de pendências para uma pessoa física (valor SemPendencias);
- A detecção de pendências financeiras associadas a uma pessoa física (valor Inadimplente).
A interface IServicoConsultaCredito representa a base para a implementação do mecanismo de comunicação com o serviço de consulta a crédito, além do meio através do qual serão criados Mocks para o teste deste processo. Tudo isto acontecerá no método ConsultarPendenciasPorCPF, cujos retornos possíveis são:
- Uma exceção, caso ocorram problemas na comunicação com o serviço;
- Nulo, em se tratando de um CPF inválido;
- Uma coleção com uma ou mais instâncias do tipo Pendencia, se a pessoa física em questão possuir pendências financeiras;
- Uma coleção do tipo Pendencia sem elementos, para pessoas físicas sem qualquer impedimento financeiro.
using System.Collections.Generic;
namespace ConsultaCredito
{
public interface IServicoConsultaCredito
{
IList<Pendencia> ConsultarPendenciasPorCPF(string cpf);
}
}
Já na próxima listagem está a definição da classe AnaliseCredito:
- Este tipo receberá em seu construtor uma instância do tipo IServicoConsultaCredito, a qual será empregada no acesso ao serviço de consulta;
- A comunicação entre a classe AnaliseCredito e tal serviço se dará no método ConsultarSituacaoCPF, que foi preparado para o tratamento dos diferentes retornos ao se invocar a operação ConsultarPendenciasPorCPF de IServicoConsultaCredito.
namespace ConsultaCredito
{
public class AnaliseCredito
{
private IServicoConsultaCredito _servConsultaCredito;
public AnaliseCredito(IServicoConsultaCredito servConsultaCredito)
{
_servConsultaCredito = servConsultaCredito;
}
public StatusConsultaCredito ConsultarSituacaoCPF(string cpf)
{
try
{
var pendencias =
_servConsultaCredito.ConsultarPendenciasPorCPF(cpf);
if (pendencias == null)
return StatusConsultaCredito.ParametroEnvioInvalido;
else if (pendencias.Count == 0)
return StatusConsultaCredito.SemPendencias;
else
return StatusConsultaCredito.Inadimplente;
}
catch
{
return StatusConsultaCredito.ErroComunicacao;
}
}
}
}
Implementando os testes
O próximo passo agora consistirá na criação de um projeto de testes chamado ConsultaCredito.Testes. Selecionar para isto o template xUnit Test Project (.NET Core), no qual será feito uso do framework xUnit para a implementação de testes de unidade:
Acrescentar ainda uma referência à biblioteca ConsultaCredito:
E adicionar o package do NSubstitute ao projeto ConsultaCredito.Testes:
Na classe Testes foram implementadas validações para os diferentes casos de teste envolvendo o uso do tipo AnaliseCredito:
- Quatro constantes (CPF_INVALIDO, CPF_ERRO_COMUNICACAO, CPF_SEM_PENDENCIAS e CPF_INADIMPLENTE) foram definidas, com as mesmas correspondendo aos diferentes números de CPF empregados em cada validação prevista pela classe Testes;
- Uma instância do tipo IServicoConsultaCredito (mock) será criada no construtor de Testes, por meio de uma chamada ao método For da classe Substitute (namespace NSubstitute);
- O Mock Object resultante será configurado, através de chamadas aos métodos ConsultarPendenciasPorCPF (que receberá o CPF específico para um caso de teste) e Returns (este último um Extension Method provido pelo framework NSubstitute, no qual será indicado o retorno esperado para cada CPF);
- As operações TestarParametroInvalido, TestarErroComunicacao, TestarCPFSemPendencias e TestarCPFInadimplente foram marcadas com o atributo Fact (namespace Xunit), de forma a permitir que o Visual Studio identifique os métodos/casos de teste a serem executados;
- O método auxiliar ObterStatusAnaliseCredito receberá um CPF e utilizará o Mock Object criado no construtor, a fim de obter o retorno a ser validado por cada um dos 4 casos de teste.
using System;
using System.Collections.Generic;
using Xunit;
using NSubstitute;
namespace ConsultaCredito.Testes
{
public class Testes
{
private const string CPF_INVALIDO = "123A";
private const string CPF_ERRO_COMUNICACAO = "76217486300";
private const string CPF_SEM_PENDENCIAS = "60487583752";
private const string CPF_INADIMPLENTE = "82226651209";
private IServicoConsultaCredito mock;
public Testes()
{
mock = Substitute.For<IServicoConsultaCredito>();
mock.ConsultarPendenciasPorCPF(CPF_INVALIDO)
.Returns((List<Pendencia>)null);
mock.ConsultarPendenciasPorCPF(CPF_ERRO_COMUNICACAO)
.Returns(s => { throw new Exception("Erro de comunicação..."); });
mock.ConsultarPendenciasPorCPF(CPF_SEM_PENDENCIAS)
.Returns(new List<Pendencia>());
Pendencia pendencia = new Pendencia();
pendencia.CPF = CPF_INADIMPLENTE;
pendencia.NomePessoa = "João da Silva";
pendencia.NomeReclamante = "Empresa XYZ";
pendencia.DescricaoPendencia = "Parcela não paga";
pendencia.VlPendencia = 700;
List<Pendencia> pendencias = new List<Pendencia>();
pendencias.Add(pendencia);
mock.ConsultarPendenciasPorCPF(CPF_INADIMPLENTE)
.Returns(pendencias);
}
private StatusConsultaCredito ObterStatusAnaliseCredito(string cpf)
{
AnaliseCredito analise = new AnaliseCredito(mock);
return analise.ConsultarSituacaoCPF(cpf);
}
[Fact]
public void TestarParametroInvalido()
{
StatusConsultaCredito status =
ObterStatusAnaliseCredito(CPF_INVALIDO);
Assert.Equal(
StatusConsultaCredito.ParametroEnvioInvalido, status);
}
[Fact]
public void TestarErroComunicacao()
{
StatusConsultaCredito status =
ObterStatusAnaliseCredito(CPF_ERRO_COMUNICACAO);
Assert.Equal(
StatusConsultaCredito.ErroComunicacao, status);
}
[Fact]
public void TestarCPFSemPendencias()
{
StatusConsultaCredito status =
ObterStatusAnaliseCredito(CPF_SEM_PENDENCIAS);
Assert.Equal(
StatusConsultaCredito.SemPendencias, status);
}
[Fact]
public void TestarCPFInadimplente()
{
StatusConsultaCredito status =
ObterStatusAnaliseCredito(CPF_INADIMPLENTE);
Assert.Equal(
StatusConsultaCredito.Inadimplente, status);
}
}
}
Executando os testes
Para executar o projeto de testes será necessário acessar o menu Tests > Run > All Tests:
Na janela Test Explorer constará o resultado da execução dos diferentes casos de teste:
Conclusão
Os frameworks de Mocking Test simulam o comportamento de objetos em diferentes cenários, dispensando os desenvolvedores da necessidade de criar implementações que seriam descartadas/desativadas num ambiente de produção. O NSubstitute exemplifica bem isto, sendo que outro ponto que pesa a seu favor está no fato do mesmo exigir menores esforços de codificação quando comparado ao Moq.















