Vinicius Quaiato

{tecnologia, conceitos, negócios, idéias, práticas, .NET, ruby, osx, ios e algo mais}

TDD: mock objects usando Moq


Moq é um framework para a criação de Mock Objects. Estes Mock Objects são muito utilizados na escrita de testes unitários para a simulação e verificação do comportamento sendo testado.Em geral utilizamos mock objects quando uma classe/método sendo testado possui uma dependência para alguma outra classe. Como os teste unitários trabalham com a questão do SUT, a mínima parte sendo testa, unicamente, criamos estes "emuladores" para estas dependências, e então conseguimos verificar a interaçõe entre estes objetos.(O código completo está no final do post para download).

Verificando se um método foi chamado

Se eu quero garantir que meu objeto chamou um método de outro objeto:

[TestClass]
public class TesteQualquer{    [TestMethod]    
public void Quando_Autentica_Tem_Que_Logar_Login_E_Senha()    {
var loggerMock = new Mock<ilogger>();
var autenticador = new Autenticador(loggerMock.Object);
    autenticador.Autenticar("login", "senha");
    loggerMock.Verify(l => l.Log("login e senha"));
    }
}
</ilogger>

Nexte exemplo, criamos um Mock para a interface ILogger, ou seja, o Moq criará um objeto que implementa esta interface e ainda nos fornecerá uma série de formas de verificarmos o que aconteceu com este objeto.Na linha 12 utilizamos o método Verify do objeto mockado para verificarmos se o método Log foi chamado com os valores corretos. Bastante simples.

Garantindo que um método seja chamado N vezes

Em algumas situações queremos saber quantas vezes um determinado método foi chamado. Ou seja, queremos garantir que ele não foi chamado, ou foi chamado uma vez, chamado pelo menos uma vez, ou muitas vezes.

[TestMethod]
public void Dados_4_Emails_Para_Relatorio_De_Vendas_Deve_Enviar_Os_4(){
var enviadorEmails = new Mock<ienviadoremail>();
var relatorioVendas = new RelatorioVendas(enviadorEmails.Object);
var emails = new List<string> { "email1", "email2", "emailN", "email2" }
;
    relatorioVendas.EnviarPorEmail(emails);
    enviadorEmails.Verify(e => e.Enviar("email1"), Times.Once());
    enviadorEmails.Verify(e => e.Enviar("email2"), Times.Exactly(2));
    enviadorEmails.Verify(e => e.Enviar("emailN"), Times.Once());
    }
</string></ienviadoremail>

Neste caso temos um Relatório de Vendas, que será enviado por email. Para que não precisemos da internet, um provedor configurado, etc (isso é cenário para outro tipo de testes) vamos "mockar" quem envia os emails.Aqui estamos usando uma lista de emails, e garantindo que cada email seja enviado para seu respectivo endereço.Fazemos isso nas linhas 10 a 12, utilizando a struct Times. Na linha 10 dizemos que o email "email1" deve ser enviado uma vez. Na linha 11 o email "email2" que aparece duas vezes na lista, deve ser enviado exatamente duas vezes. E na linha 12 o email "emailN" deve ser enviado apenas uma vez.Simples.

Configurando um valor de retorno

Imagine a situação onde queremos que na chamada de um objeto mockado desejamos ter um valor de retorno. Podemos fazer isso de forma simples, garantindo assim que o desacoplamento de nossas classes possue uma cobertura de testes boa e que verifica vários cenários:

[TestMethod][ExpectedException(typeof(AppErrorException))]
public void Quando_LogError_Possui_Erro_LogMonitor_Deve_Lancar_Excecao(){
var logReader = new Mock<ilogreader>();
    logReader.Setup(r => r.Read())        .Returns("Erro: Acesso não autorizado.");
var logMonitor = new LogMonitor(logReader.Object);
    logMonitor.Inspect();
    }
</ilogreader>

Neste exemplo estamos utilizando um mock para uma interface de algo que lê um log. Configuramos nosso mock para que quando um método específico seja chamado.Na linha 6 chamamos o método Setup e informamos que queremos configurar o método Read da nossa interface. Na linha 7 chamamos o método Returns e então configuramos o valor de retorno. Isso quer dizer que "quando for chamado o método Read retorne 'Erro: Acesso não autorizado'".Neste exemplo crio um mock para uma interface que abstrai a leitura de logs. Temos uma classe que depende desta abstração, e neste caso testamos se quando um erro está presente no log, esta classe dispara uma exceção. Configurando os valores de retorno no mock faz esta tarefa ser simples para testar os vários cenários em que a classe LogMonitor possa estar.

A deixa

Bom galera, é isso. Existem muitas outras funcionalidades no Moq, e é claro que vou deixar um pouco para vocês explorarem também, senão não tem graça.Espero que tenha ajudado a mostrar alguns usos de mock objects, e acima de tudo, tenha instigado um pouco a prática de testes no dia-a-dia de vocês.Vamos continuar com a cmapanha: "Um teste por dia no seu legado".

Um teste é melhor do que nenhum teste



Baixe a solution completa aqui.

Abraços, Vinicius Quaiato.

Voltar

Fork me on GitHub