Testando mensagens em Exceptions com MSTest

18 ago

Pessoal,

De volta ao blog, finalmente.

Todos devem saber que nos testes de unidade, quando queremos verificar se uma determinada exceção foi disparada no método que está sendo testado, se estivermos utilizando o MSTest (ferramenta de testes nativa do Visual Studio) utilizamos o atributo [ExpectedException]. Mais ou menos assim (não reparem no teste, trata-se de um exemplo):

[TestMethod]
[ExpectedException(typeof(MyBusinessException))]
public void Salvar_ComEmailDuplicado_LevantaExcecao()
{
   // Arrange
   var umaClasseDeNegocio = new ClasseDeNegocio();
   var umaEntidade = new UmaEntidade
   {
      Nome = "Nome válido",
      Email = "email@existente.com",
      Endereco = "Endereco com tamanho valido",
      Quantidade = 5
   };

   // Act
   umaClasseDeNegocio.Salvar(umaEntidade);
}

Porém, várias pessoas perguntam como fazer para, além de verificar se a exceção foi disparada, testar também a mensagem da exceção. Normalmente antes de responder, pergunto: Tem certeza que precisa testar isso?

Pergunto isso porque ficar testando pela mensagem da exceção normalmente me parece um code smell, pois em boa parte dos casos isso pode significar que poderíamos estar fazendo um tratamento de exceção mais específico, ao invés de ficar levantando exceções “genéricas” com mensagens diferentes. Mas, isso é um outro assunto – Vamos admitir que nesse caso o tratamento de exceções esteja adequado.

Infelizmente o MSTest não nos disponibiliza um atributo que permita verificar a mensagem da exceção (O NUnit disponibiliza). Assim, uma forma de fazer essa verificação é dessa forma (mais uma vez, trata-se de um exemplo):

[TestMethod]
public void Salvar_ComEmailDuplicado_LevantaExcecao()
{
   // Arrange
   var umaClasseDeNegocio = new ClasseDeNegocio();
   var umaEntidade = new UmaEntidade
   {
      Nome = "Nome válido",
      Email = "email@existente.com",
      Endereco = "Endereco com tamanho valido",
      Quantidade = 5
   };

   // Act
   try
   {
      umaClasseDeNegocio.Salvar(umaEntidade);

      // Assert
      Assert.Fail("Nenhuma exceção lançada");
   }
   catch (MyBusinessException ex)
   {
      // Assert
      Assert.AreEqual("Este e-mail já está cadastrado", ex.Message);
   }
}

Horrível. Uma outra forma de fazer, e é o que eu faço, é criar um atributo herdando da classe ExpectedExceptionBaseAttribute (o atributo [ExpectedException] também herda dela) e verificar o tipo da exceção e da mensagem. Assim:

public class ExpectedExceptionMessageAttribute
                                         : ExpectedExceptionBaseAttribute
{
   protected Type ExceptionType { get; set; }
   protected string ExceptionMessage { get; set; }

   public ExpectedExceptionMessageAttribute(Type exceptionType,
                                            string exceptionMessage)
   {
      this.ExceptionType = exceptionType;
      this.ExceptionMessage = exceptionMessage;
   }

   public ExpectedExceptionMessageAttribute(Type exceptionType, 
                                            string exceptionMessage,
                                            string noExceptionMessage)
   : base(noExceptionMessage)
   {
      this.ExceptionType = exceptionType;
      this.ExceptionMessage = exceptionMessage;
   }

   protected override void Verify(Exception exception)
   {
      if (exception.GetType() != this.ExceptionType)
      {
         var errorMessage
                 = string.Format("Test method {0} did not throw expected
                                  exception {1}.",
                                  this.TestContext.TestName,
                                  this.ExceptionType.FullName);
         throw new AssertFailedException(errorMessage);
      }

      if (exception.Message != this.ExceptionMessage)
      {
         var errorMessage
                 = string.Format("Test method {0} did not throw expected
                                  exception with message '{1}'.
                                  The message was '{2}'.", 
                                  this.TestContext.TestName,
                                  this.ExceptionMessage,
                                  exception.Message);
         throw new AssertFailedException(errorMessage);
      }
   }
}

Assim, aquele teste que verifica a mensagem ficaria assim:

[TestMethod]
[ExpectedExceptionMessage(typeof(MyBusinessException), "Este e-mail já está cadastrado")]
public void Salvar_ComEmailDuplicado_LevantaExcecao()
{
   // Arrange
   var umaClasseDeNegocio = new ClasseDeNegocio();
   var umaEntidade = new UmaEntidade
   {
      Nome = "Nome válido",
      Email = "email@existente.com",
      Endereco = "Endereco com tamanho valido",
      Quantidade = 5
   };

   // Act
   umaClasseDeNegocio.Salvar(umaEntidade);
}

Bem melhor, IMHO.

Forte abraço.

Deixe um comentário