Dev (Back & Front)ARTIGO

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.

Referências

Atua como consultor em atividades voltadas ao desenvolvimento de sistemas há mais de 15 anos. Microsoft MVP (Most Valuable Professional) e participante do programa MTAC (Multi-Plataform Technical Audience Contributor). Bacharel em Sistemas de Informação, com Especialização em Engenharia de Software e MBA em Business Intelligence. Também é palestrante e autor técnico em portais e revistas especializadas, com foco em tecnologias Microsoft e boas práticas na área de software.

Ver perfil