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());
Posts em Português
Posts in English
Muito bom, várias vezes vi testes de funcionalidades que enviam e-mails serem deixados de lado. Agora não tem mais desculpas.
Pingback: Servidor De Email Local No Ubuntu Com Exim | Destaqueblog