2 (boas) formas de testar clientes JavaMail

Sem sombra de dúvidas, a API Java mais usada para resolver a questão do envio e recebimento de emails é o JavaMail. Apesar de existirem outras opções, como o Spring Mail e Commons Email, no fim das contas é o próprio JavaMail quem faz todo o trabalho sujo, já que tais APIs são baseadas no próprio JavaMail.

Apesar de dar conta do recado, a referida API conta com alguns problemas bastante complicados, dentre os quais eu destaco o design (horrível, diga-se de passagem) de suas classes e interfaces, que parecem fazer o possível para dificultar a testabilidade dos seus “clientes”.

Entendendo o problema

Uma técnica de testes bastante utilizada atualmente consiste no uso de frameworks para Mocking de objetos. Esses frameworks permitem testar código que faça uso de quaisquer recursos externos e que são, por natureza, difíceis de testar. Classes que necessitam acessar recursos de infraestrutura, como arquivos em disco, impressoras e bancos de dados, são ótimas candidatas a serem “mockadas”.

O grande problema é que, para que tais frameworks possam ser usados, o código a ser “mockado” tem de seguir algumas regrinhas de codificação. Por exemplo, alguns frameworks não conseguem “mockar” uma classe concreta; outros por sua vez, conseguem “mockar” classes concretas desde que elas não sejam finais.

Bom, só para resumir, o ideal seria que o código todo — tanto das classes de infraestrutura quanto das classes que fazem uso das primeiras — seja orientado a interfaces. Isso é necessário pois grande parte dos frameworks Java para Mocking de objetos se apoiam na API Proxy para fazer seu trabalho e, como muitos já devem saber, não é possível criar proxies de classes contretas através do uso dessa API.

Para ilustrar melhor o que estou tentando dizer, segue um exemplo de código que envia um e-mail com a API JavaMail:

Properties props = System.getProperties();
props.put("mail.smtp.host", "smtp.middlenet.com");
Session session = Session.getDefaultInstance(props);
 
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress("Frodo", "frodo@theshire.com"));
message.addRecipient(RecipientType.TO, new InternetAddress("Gandalf", "blog@gandalf.com"));
message.setSubject("Be careful");
message.setText("Sauron is looking for the One Ring!");
 
Transport.send(message);

Veja, por exemplo, que o objeto Session é obtido através da chamada ao método estático getDefaultInstance(); de modo semelhante, temos também a chamada ao método estático send(), na última linha do código. Ah claro, sem contar que a classe Session é final! Como podemos fazer para testar o que vai para e o que volta da API JavaMail?

Uma solução

Apesar de não ser possível testar o código anterior usando um framework para Mocking de objetos “tradicional”, felizmente há uma luz no fim do túnel.

Uma opção bastante interessante é a utilização de um servidor SMTP “de mentirinha”, para o qual as mensagens são enviadas. Assim, podemos verificar o que acontece quando o código “conversa” com um servidor.

A boa notícia é que não precisamos implementar um servidor SMTP do zero! Uma opção de servidor como esse é o SubEthaSMTP Wiser (!). Com ele, podemos subir um servidor SMTP numa porta qualquer. Então, basta configurar nossa classe de envio de e-mails de modo que tal servidor seja usado no lugar do servidor de produção. As mensagens enviadas a esse servidor podem ser consultadas e verificadas através de asserções no código de testes.

Usando o SubEthaSMTP Wiser com o Maven

Se você usa o Maven, então basta adicionar a dependência no seu POM para tornar as classes do SubEthaSMTP Wiser disponíveis no seu ambiente de testes:

<dependency>
    <groupId>org.subethamail</groupId>
    <artifactId>subethasmtp-wiser</artifactId>
    <version>1.2</version>
    <scope>test</scope>
</dependency>

Talvez você veja uma mensagem de erro indicando que a dependência javax.activation:1.0.2 não foi encontrada. Se isso acontecer, você deverá baixar essa versão do site da Sun e fazer a instalação manualmente no seu repositório local. (o Maven informa qual comando rodar para fazer essa instalação).

Exemplo de uso

Usar esse servidor é uma tarefa bem simples. A maior dificuldade é configurar a nossa classe de envio de e-mails para se conectar nesse servidor em vez do servidor de produção. Veja só:

EmailMessage message = ... ; // objeto com os dados da mensagem
 
/* inicia o servidor */
Wiser server = new Wiser();
server.setPort(2500);
server.start();
 
/* configura o cliente */
Properties prop = System.getProperties();
prop.setProperty("mail.smtp.host", "localhost");
prop.setProperty("mail.smtp.port", "2500");
session = Session.getDefaultInstance(prop);
 
EmailSender sender = new JavaMailEmailSender(session); // minha classe
sender.send(message); // o parâmetro contém os dados de envio e corpo da mensagem
 
server.stop(); // pára o servidor
 
/* verifica o que foi enviado */
assertEquals(1, server.getMessages().size());
assertEquals("Sauron is looking for the One Ring!", server.getMessages().get(1).getMimeMessage().getContent());

Nas primeiras linhas iniciamos o servidor. Em seguida, configuramos a Session do JavaMail para que ela se conecte ao nosso servidor “de mentirinha”. A partir desse ponto, tudo funciona como de costume, com a vantagem de podermos realizar asserções para verificar se as mensagens chegaram ao servidor da forma esperada. Isso é especialmente útil quando utilizamos algum tipo de processador de templates durante o envio das mensagens, permitindo que possamos testar o que exatamente chegou ao servidor.

Para quem não gostou dessa abordagem…

Existe ainda uma outra opção que podemos utilizar para testar classes que fazem uso do JavaMail. Trata-se de um projeto chamado mock-javamail que, ao contrário do SubEthaSMTP Wiser, não usa um servidor SMTP “de mentirinha” para receber as mensagens; o que ele faz é configurar o JavaMail em runtime de modo que as mensagens enviadas sejam armazenadas em memória.

Usando o mock-javamail com o Maven

Primeiramente precisamos adicionar o repositório Maven do java.net:

<repositories>
    <repository>
        <id>java.net2</id>
        <url>http://download.java.net/maven/2</url>
    </repository>
</repositories>

Feito isso, basta declarar a dependência:

<dependency>
    <groupId>org.jvnet.mock-javamail</groupId>
    <artifactId>mock-javamail</artifactId>
    <version>1.3</version>
</dependency>

Exemplo de uso

Este projeto é mais simples de se utilizar que o SubEthaSMTP Wiser justamente pelo fato de não precisarmos iniciar um servidor SMTP. Isso fica evidente no exemplo abaixo, que é uma adaptação do código anterior:

EmailMessage message = ... ; // objeto com os dados da mensagem
 
/* configura o cliente */
Properties prop = System.getProperties();
prop.setProperty("mail.smtp.host", "localhost");
prop.setProperty("mail.smtp.port", "2500");
session = Session.getDefaultInstance(prop);
 
EmailSender sender = new JavaMailEmailSender(session); // minha classe
sender.send(message); // o parâmetro contém os dados de envio e corpo da mensagem
 
/* verifica o que foi enviado */
assertEquals(1, Mailbox.get("gandalf@asskickers.com").size());
assertEquals("Sauron is looking for the One Ring!", Mailbox.get("gandalf@asskickers.com").get(0).getContent());

Sobre Daniel Martins

Fundador da Destaquenet, ele é graduado em Sistemas de Informação e desenvolve softwares como hobby e profissão desde 2000. Especializado na plataforma Java, ele utiliza a tecnologia há vários anos, sendo programador e desenvolvedor web certificado pela Sun Microsystems, recentemente adquirida pela Oracle. Também se interessa por assuntos ligados à cultura open source, metodologias ágeis, engenharia de software, frameworks e linguagens dinâmicas tais como Python, Ruby e Smalltalk.
Esta entrada foi publicada em Português, Programação, Tutoriais e marcada com a tag , , , , , , , , , . Adicione o link permanente aos seus favoritos.

2 respostas a 2 (boas) formas de testar clientes JavaMail

  1. Muito bom, várias vezes vi testes de funcionalidades que enviam e-mails serem deixados de lado. Agora não tem mais desculpas.

  2. Pingback: Servidor De Email Local No Ubuntu Com Exim | Destaqueblog

Deixe um Comentário

O seu endereço de email não será publicado Campos obrigatórios são marcados *

*

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">