Loading...

E-mails validation in end-to-end tests of web application

 Have you ever considered to validate e-mail notifications send by web application in your end-to-end tests. This is not very popular approach because in most cases such validation can be done on other tests level and it requires external elements to be able to work with e.g. tests written in Selenium. If nevertheless it seems to be valuable for you then you should read this article.

There are two main approaches to validate emails send by web application in the tests. First and easier is to run SMTP server in @Before method of your tests and configure the application to use the server on with tests are running as a SMTP server. This approach is not ideal when you run your tests in CI tool with different agents because the machine on which tests are running can change (so SMTP server of tested application depends on server on which tests are running). In such situation better solution is to use standalone SMTP and POP3 server (e.g. GreenMail) which is running on any dedicated machine and tested application is configured to use it as a SMTP server. Then we just have to write classes to get mails from this server in our tests.

To make all those puzzles working we have to run GreenMail standalone version and write classes which will work with e-mail server. To run GreenMail standalone version we have to just download the latest version of application from project home page and execute the command:

java -Dgreenmail.setup.all -Dgreenmail.hostname=%IP_ADDRESS% -jar greenmail-standalone-1.5.6.jar


The first class we need is the one that represent the e-mail. In the class we have fields that represent subject, list of addressee, list of senders and the content of the mail. To get all those excluding content of the e-mail we can use dedicated methods of Message class. Getting the content is a little bit more complicated and is defined in getTextFromMessage(Message message) method. I am additionally wrap the content with <html></html> tags to make it easier to parse with xml parser.

package com.testcraftsmanship.model.mail;

import lombok.Getter;

import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMultipart;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import static org.apache.http.entity.ContentType.TEXT_HTML;
import static org.apache.http.entity.ContentType.TEXT_PLAIN;

@Getter
public class Email {
    private String subject;
    private List<Address> from;
    private List<Address> to;
    private String content;

    public Email(Message msg) throws MessagingException, IOException {
        this.subject = msg.getSubject();
        this.from = Arrays.asList(msg.getFrom());
        this.to = Arrays.asList(msg.getAllRecipients());
        this.content = wrapContentWithHtmlTag(getTextFromMessage(msg));
    }

    private static String getTextFromMessage(Message message)
            throws MessagingException, IOException {
        String result = "";
        if (message.isMimeType(TEXT_PLAIN.getMimeType())) {
            result = message.getContent().toString();
        } else if (message.isMimeType("multipart/*")) {
            MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
            result = getTextFromMimeMultipart(mimeMultipart);
        }
        return result;
    }

    private static String getTextFromMimeMultipart(MimeMultipart mimeMultipart)
            throws MessagingException, IOException {
        StringBuilder result = new StringBuilder();
        int count = mimeMultipart.getCount();
        for (int i = 0; i < count; i++) {
            BodyPart bodyPart = mimeMultipart.getBodyPart(i);
            if (bodyPart.isMimeType(TEXT_PLAIN.getMimeType())) {
                result.append("\n").append(bodyPart.getContent());
                break;
            } else if (bodyPart.isMimeType(TEXT_HTML.getMimeType())) {
                String html = (String) bodyPart.getContent();
                result.append("\n").append(html);
            } else if (bodyPart.getContent() instanceof MimeMultipart) {
                result.append(getTextFromMimeMultipart((MimeMultipart) bodyPart.getContent()));
            }
        }
        return result.toString();
    }

    private String wrapContentWithHtmlTag(String xmlContent) {
        return "<html>" + xmlContent + "</html>";
    }
}


The most important part in this solution is the class which is responsible for working with e-mail server. With use of the API of that class we should be able to get e-mails (method getEmailsList(String emailAddress)) and remove e-mails (method removeAllEmails (String emailAddress)) send to desired user. As I am using GreenMail as a SMTP,POP3 server so when any mail is send to that server, then user with that email is created and password is exactly the same as an e-mail address - that's why getDefaultPasswordForEmail(String emailAddress) method returns e-mail address. Additionally I have created method which gets the e-mails for desired user every 500 milliseconds until get at least one e-mail with desired title. I think it is valuable because I expect some delays between performing action which triggers mail sending in the web application and getting the mail to the server.

package com.testcraftsmanship.model.mail;

import com.sun.mail.pop3.POP3Store;
import com.testcraftsmanship.model.configuration.Configuration;
import org.openqa.selenium.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.mail.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;

import static com.testcraftsmanship.model.configuration.Constants.ACTION_TIMEOUT_IN_SECONDS_FOR_MAIL_RECEIVING;

public class MailReceiver {
    private static final Logger LOGGER = LoggerFactory.getLogger(MailReceiver.class);

    public static void removeAllEmails(String emailAddress) {
        POP3Store emailStore = null;
        Folder emailFolder = null;
        final String password = getDefaultPasswordForEmail(emailAddress);
        try {
            emailStore = getMailStore();
            emailStore.connect(emailAddress, password);
            emailFolder = emailStore.getFolder("INBOX");
            emailFolder.open(Folder.READ_WRITE);

            for (Message msg : emailFolder.getMessages()) {
                msg.setFlag(Flags.Flag.DELETED, true);
                LOGGER.debug("Mail with title {} has been removed", msg.getSubject());
            }
        } catch (MessagingException e) {
            LOGGER.warn("Problem with opening e-mail folder: " + e.getMessage());
        } finally {
            closeAndRemoveMarkedAsDeleted(emailFolder);
            close(emailStore);
            LOGGER.info("E-mails for user " + emailAddress + " has been removed from server");
        }
    }

    public static List<Email> getEmailsList(String emailAddress) {
        final String password = getDefaultPasswordForEmail(emailAddress);
        List<Email> emailsList = new ArrayList<>();
        POP3Store emailStore = null;
        Folder emailFolder = null;
        try {
            emailStore = getMailStore();
            emailStore.connect(emailAddress, password);
            emailFolder = emailStore.getFolder("INBOX");
            emailFolder.open(Folder.READ_ONLY);

            for (Message msg : emailFolder.getMessages()) {
                try {
                    emailsList.add(new Email(msg));
                } catch (IOException e) {
                    LOGGER.warn("Problem with receiving e-mail: " + e.getMessage());
                }
            }
        } catch (MessagingException e) {
            LOGGER.warn("Problem with opening e-mail folder: " + e.getMessage());
        } finally {
            close(emailFolder);
            close(emailStore);
        }
        return emailsList;
    }

    public static List<Email> waitForEmailsWithTitleToBeReceived(String emailAddress, String title) {
        final long timeout = new Date().getTime() + ACTION_TIMEOUT_IN_SECONDS_FOR_MAIL_RECEIVING * 1000;
        final long pollingIntervalInMillis = 500;
        while (timeout > new Date().getTime()) {
            List<Email> desiredMailsList = getEmailsList(emailAddress).stream()
                    .filter(email -> email.getSubject().equals(title)).collect(Collectors.toList());
            if (!desiredMailsList.isEmpty()) {
                return desiredMailsList;
            }
            try {
                Thread.sleep(pollingIntervalInMillis);
            } catch (InterruptedException e) {
                LOGGER.warn("Exception while waiting... " + e);
            }
        }
        throw new TimeoutException("Could not find e-mail with title '" + title + "'");
    }

    private static String getDefaultPasswordForEmail(String emailAddress) {
        return emailAddress;
    }

    private static void close(POP3Store pop3Store) {
        try {
            if (pop3Store != null) {
                pop3Store.close();
            }
        } catch (MessagingException e) {
            LOGGER.warn("Problem with closing folder");
        }
    }

    private static void close(Folder folder) {
        try {
            if (folder != null) {
                folder.close(false);
            }
        } catch (MessagingException e) {
            LOGGER.warn("Problem with closing folder");
        }
    }

    private static void closeAndRemoveMarkedAsDeleted(Folder folder) {
        try {
            if (folder != null) {
                folder.close(true);
            }
        } catch (MessagingException e) {
            LOGGER.warn("Problem with closing folder");
        }
    }

    private static POP3Store getMailStore() throws NoSuchProviderException {
        Properties properties = new Properties();
        properties.put("mail.pop3.host", Configuration.getConfiguration().getApplicationPop3Server());
        Session emailSession = Session.getDefaultInstance(properties);
        return (POP3Store) emailSession.getStore("pop3");
    }
}


When we have those classes ready we can create our first test which uses those classes. Before each test we are removing all e-mails send to address used in tests. In the test case we test that when we log in to the web application as an administrator (USER_MANAGEMENT_ADMIN) and create the new user (USER_WITH_CORRECT_DATA) then administrator of web application should get only one e-mail with title "User {user_name} has been created".

private static final String USER_MANAGEMENT_ADMIN = "admin@testcraftsmanship.com";

@Before
public void setUp() {
   MailReceiver.removeAllEmails(USER_MANAGEMENT_ADMIN);
}

@Test
public void emailShoulBeSendToAdminAfterCreatingUser() {
   final String expectedMailTitle = "User " + USER_WITH_CORRECT_DATA.getUserName() + " has been created";

   loginToUserManagementPageAs(USER_MANAGEMENT_ADMIN)
         .createUser(USER_WITH_CORRECT_DATA);

   List<Email> emailsList = MailReceiver
         .waitForEmailsWithTitleToBeReceived(USER_MANAGEMENT_ADMIN, expectedMailTitle);

   assertThat("More then one mail has been send", emailsList, hasSize(1));
}


It is just a basic scenario but explains how e-mails validation can be handled in end-to-end tests. To make it more valuable we still have to create email content parser but its implementation depends on mails construction. It is required to full validation of e-mail data correctness - it is of course a different story.