2 (boas) formas de testar seu código JavaMail
Uma constante no desenvolvimento de qualquer aplicação web é o envio de e-mails. Ah, o usuário se registrou? E-mail nele! O usuário trocou de senha? E-mail nele! O pedido foi recebido com sucesso? Em… chega, vai!
Sem sombra de dúvidas, a API mais usada para resolver essa questão é o JavaMail. Também existem algumas opções, como o Spring Mail e Commons Email, mas, no fim das contas, é o próprio JavaMail quem faz o serviço sujo (tais APIs são feitas sobre o próprio JavaMail).
Apesar de dar conta do recado, a referida API conta com alguns problemas bastante chatos, dentre os quais eu destaco o design de suas classes e interfaces (existem 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, em unidade, um 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, por exemplo, são ótimas candidatas para a criação de testes apoiados no uso de objetos Mock.
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:
-
-
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", "gandalf@asskickers.com"));
-
message.setSubject("Be careful");
-
message.setText("Sauron is looking for the One Ring!");
-
-
Transport.send(message);
-
Holy crap! 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…
São em momentos como esse que precisamos dar uma improvisada.
Mesmo não sendo possível testar o código mostrado anteriormente com um framework para Mocking de objetos, ainda existem algumas opções disponíveis.
Uma opção bastante interessante é a utilização de um servidor SMTP “de mentirinha”, para o qual as mensagens são enviadas. Devo dizer que, após usar essa estratégia para testar códigos de envio de e-mail, eu achei até mais interessante do que o fazer através de mocks (veremos mais adiante que isso, de certo modo, é possível de ser feito). Dessa forma, podemos verificar o que acontece quando o código “conversa” com um servidor, o que na minha opinião torna o teste um pouco mais confiável.
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 basicão numa porta qualquer. Então, basta configurar nossa classe de envio de e-mail 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 */
-
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 */
-
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());
-
Conclusão
Primeiramente, demos uma olhada no SubEthaSMTP Wiser, um servidor SMTP “de mentirinha” que nos permite verificar exatamente como a mensagem chegaria caso tivesse sido enviada para um servidor SMTP “normal”.
No entanto, esse negócio todo pode parecer gambiarra para alguns. Por isso, o framework mock-javamail foi apresentado como alternativa ao primeiro. Este, ao contrário do SubEthaSMTP Wiser, permite a verificação das mensagens enviadas pelo JavaMail sem a necessidade de um servidor SMTP.
De qualquer forma, as duas alternativas cumprem seu papel, que é ajudar a solucionar o problema da péssima testabilidade da API JavaMail.
UPDATE: disponibilizei o projeto para download na página de aplicações aqui do blog.
Foto por: Narisa.
Tags: email, java, javaee 5, javamail, maven, mock-javamail, mocking, smtp, subethasmtp wiser, testes

16 de outubro de 2007 às 7:42 am
Não tinha um coelhinho de outra cor não?
uhUHAUhAUhAUhau
16 de outubro de 2007 às 10:14 am
Todos esses coelhos de pilha são rosa… eu até mudei a cor de um desses coelhos no GIMP, mas perdeu a característica, saca. auhuaha Mas fico boiola mesmo.. vou dar um jeito nessa fita aí depois.
16 de outubro de 2007 às 12:08 pm
Pronto. Melhorou?
23 de outubro de 2007 às 10:11 am
Fala Daniel, parabens pelo post!!!
Não consegui fazer funcionar de nenhuma maneira, segue os dois fontes:
EmailMock.java
EmailTest.java
Daniel, o que você acha de compartilhar o projeto inteiro? Já que esta usando Maven2 o seu projeto deve está com no maximo 50kb..
[]’s!!!
23 de outubro de 2007 às 10:15 am
É uma excelente idéia, Alexandre!
Assim que eu disponibilizar o projeto para download, eu te aviso por e-mail. Até amanhã eu resolvo isso.
[]s
23 de outubro de 2007 às 9:50 pm
Daniel, dito e feito..!
Grato!
25 de outubro de 2007 às 11:58 am
Opa… agora está bem melhor… ahuahuhauauh
28 de outubro de 2007 às 8:30 pm
Daniel, eu nunca tinha usado nenhum mock, pelo que eu vi basta o jar estar no classpath ou no pom.xml que ele “mocka” qualquer class?!
Eu reparei isso quando tentei entender como o transport era do tipo mock no debug e também minha class que era a SenderMail (class a ser testada , ou seja , a original)estava sendo mockada sem esta sendo usado nenhum mock em seu codigo…
Quando comentei do pom.xml a linha que dependia de mock foi enviado o email numa boa..
Confirma esse comentario???
——–
Ja conseguiu enviar email pelo Gmail por alguma implementacao a seguir , javamail e commons-email?
28 de outubro de 2007 às 8:57 pm
Confirmo.
Tanto é que, na aplicação de exemplo que eu disponibilizei há alguns dias, eu tive que usar um ‘hack’ para permitir que o mesmo código pudesse ser testado tanto com o mock-javamail como com o SubEthaSMTP Wiser.
Isso é necessário porque o mock-javamail sobrescreve as opções padrão do JavaMail, setando um provider customizado. Esse provider não usa SMTP; ele simplesmente pega as mensagens que chegam e as coloca em uma Mailbox ‘virtual’. (veja aqui mais informações).
Então, para permitir que o SubEthaSMTP Wiser funcionasse, eu tive de setar manualmente o provider SMTP padrão; do contrário, as mensagens nunca chegariam ao servidor SMTP.
28 de outubro de 2007 às 9:01 pm
Nunca tentei usar o JavaMail com servidores do GMail (e nem com qualquer servidor que funcione com SSL). Mas com certeza deve existir algo que permita fazer isso.
Na verdade, o Commons-Email (assim como o Spring Mail e grande parte das APIs de envio de e-mail) se baseia no próprio JavaMail.. é tipo um Wrapper que visa facilitar o seu uso.
1 de novembro de 2007 às 2:16 pm
O Gmail usa SSL, consegui usar com javaMail, o codigo fica um lixo…
Mais esta funcionando..
Com commons não consegui ainda, ta dificil setar os SSL no commons.
Nunca usei Spring-emails, ficou bom o Wrapper?
1 de novembro de 2007 às 6:09 pm
Dá uma olhada!
8 de janeiro de 2008 às 4:39 pm
Daniel, com o JMockit é possível “mockar” métodos estáticos. Você pode utilizá-lo junto com Junit + EasyMock + JMockit…
Espero ter ajudado…
JMockit - https://jmockit.dev.java.net/
8 de janeiro de 2008 às 4:57 pm
Valeu pela dica Marcelo!
Apesar de não parecer um projeto com a mesma maturidade de um JMock ou EasyMock, ele lida de forma interessante com algumas limitações que podem ser chatas para quem está tendo de testar um código mal projetado.
[]s