Automatické testování odeslání emailu

Jistě jste také už mnohokrát, stejně jako já, řešili problém, jak spolehlivě automaticky otestovat, že vaše aplikace správně odeslala email s konkrétním obsahem na konkrétní emailovou adresu. Problém je to zapeklitý a dosud jsem ho dokázal řešit jen těmito způsoby:

  • udáním testovací schránky a automatickým výběrem této schránky (např. přes protokol POP3)
  • vytvořením mock objektu, který mi zastoupil třídu starající se o odeslání emailu (tzn. k žádnému emailu fyzicky v testu nedošlo)

Oba dva přístupy mají svá úskalí. Ten první velmi komplikuje běh testu - musíme naprogramovat další funkcionalitu, která nám vybere emaily jiným protokolem, musíme řešit možnost, že se email někde pozdrží, musíme fyzicky nějakou schránku mít, musíme vyřešit to, jak při opakovaném spouštění testů rozeznáme, že do schránky přišel právě ten mail, na kterém v testu čekáme. Fakticky se potom často stane, že v testu samotném je víc chyb, jak v tom jednoduchém kódu, který se snažíme otestovat.

Druhý přístup je jednodušší a test s ním obvykle po vyladění spolehlivě prochází. Nicméně tento přístup nezaručuje, že v reálném nasazení,kdy místo mock objektu, bude skutečný objekt zajišťujicí odeslání emailu nedojde k chybě (např. díky tomu, že náš mock objekt nedostatečně dobře imituje chování reálného objektu).

Do této doby jsem tedy odesílání emailů ověřoval "ručně". Automatické testy sice odesílaly emaily, ale ty šly na moji schránku a v ní jsem zkontroloval, jestli očekávaný email v očekávané době došel. Skvělá věc, když spouštíte testy ručně, ovšem horší v případě, že vám testy spouští integrační server. To se vaše schránka pěkně plní a vy stejně nemáte šanci vyhodnotit, zda je vše jak má.

SubEthaSMTP Server

A nyní přichází rozřešení mého dlouholetého problému. SubEthaSMTP Server je SMTP server napsaný v Javě, který má integrovanou podporu pro jednoduché testování a ten můj problém vyřešil.

V celém řešení není žádná záhada, fígl nebo vychytávka. V rámci svého testu si normálně spustíte vlastní instanci SMTP Serveru, která se bindne na konkrétní port a chová se jako klasický SMTP Server. Váš kód tedy fakticky email odešle - jen máte SMTP Server pod svojí kontrolou. Z instance SMTP Serveru si maily můžete jednoduše vyzvednout a nějak je zpracovat. V testech obvykle stačí jen ověřit, že nějaké došly a případně proklepnout jejich obsah.

Krásné na tom celém je to, že nasazení je naprosto směšně jednoduché. Stačí vám k tomu jen tyto věci:

  • k projektu přilinkovat Wiser z projektu SubEthaMail - pro Maven 2 projekty stačí přidat dependency:

    
       <dependency>
          <groupId>org.subethamail</groupId>
          <artifactId>subethasmtp-wiser</artifactId>
          <version>1.0.3</version>
          <scope>test</scope>
       </dependency>
       
    
  • pak už stačí jen napsat test:

    
       public void testResendActivationEmail() throws Exception {
             Wiser wiser = new Wiser();
             wiser.setPort(2500); // výchozí port je 25
             wiser.start();
             //tady spusťte metodu, která vyvolá odeslání emailu
             //důležité je zajistit odesílání emailů přes SMTP server localhost:2500
             wiser.stop()
             //ověřte, že byl odeslán právě jeden mail
             assertTrue("No messages arrived! No of messages = " + wiser.getMessages().size(), wiser.getMessages().size() == 1);
             MimeMessage message = wiser.getMessages().get(0).getMimeMessage();
             //ověříme třebas recipienta
             assertEquals("pop@fg.cz", message.getRecipients(MimeMessage.RecipientType.TO)[0].toString());
             //nebo předmět emailu
             assertEquals("Aktivační email", message.getSubject());
             //vyčístíme server od zpráv (další test může klidně recyklovat stejný objekt Wiseru)
             wiser.getMessages().clear();
       }
       
    

Prosté a funkční. Na tento způsob jsem narazil v článku Using Groovy to send email na OnJava - možná jej řada z vás k testování této funkcionality používá, ale pro mne to byla cenná novinka.