Functional testing series
- Blackbox testing microservices
- Graybox testing – Control your dependencies
- Bring functional tests closer to business with Cucumber
- Functional testing of email communication
In the post about Blackbox testing microservices we set the grounds for testing of applications over API and treating them as Blackbox. Then, we added hints how to Control external dependencies with WireMock and we wrapped up the whole story with Cucumber and How to bring functional tests closer to business. This post will continue that story and refer to the subject of in-app email communication and how to test it.
Notifications to users following significant events in a system are a part of almost every modern system. Those notifications are usually internal, but there is a need to notify people over the email that something has happened, so they can return to the application and check it out. Email is a medium beyond our control and it is always hard to verify if a person got the right email and if the content is right, especially when sending dynamic emails.
We have done some brainstorming and decided to make a transition to testable email flow following a couple of steps which will be further explained:
wrap email sending in interface
use fake implementation in test environment which will send email over API instead of actual mail server
receive email on endpoint on functional testing application side
- verify email content
Sending of emails
The first thing to do was to hide sending of emails behind interface. We created EmailService which was pretty simple and had a single method sendEmail(Email email). We were using Spring so it was easy for us to have different implementations based on the environment, just annotateBeen with @Profile and Spring will take care of the rest. So we created production implementation which used Mandrill to send emails. We created one more implementation which would send the same email to endpoint under our control in the functional testing application. This way we can verify if our application is sending email and we can verify its content.
HTML emails are large and have additional HTML elements, making them slower on wire and harder to parse for significant content, so we decided to send only a plain text email. This was enough for us to verify two important things, whether the email was sent and the content it had. Our implementation is already sending plaintext and HTML mail anyway because of email client compatibility, so we can fallback to plain text if needed, so we did not have many changes to that part of our application.
Receiving of emails
On the receiving side, we had a couple of challenges as well. The first one was receiving itself. We needed some light persistence mechanism so we can parse emails and use information from it in the flow. This is best explained through a sign up example. In order to sign up, you need to send all information to sign up endpoint, then you receive a verification email and when you click on the activation link you are fully verified. One of our scenarios included testing the whole flow. In order to do that we needed to fetch email, parse activation link and do activation POST request. We decided to do all this logic on the functional testing application by exposing the endpoint, and storing emails in memory, in Map with email address as key, so we can internally poll that map for changes in the functional testing application. In memory, implementation was good enough because we needed email only while the functional test was running and that is the reason why we opted for the simplest solution.
The final challenge was polling; we added the network factor because of API, and we could not be certain regarding the specific time when the email was received. We needed a polling mechanism which will poll the email repository for new email which would trigger the test to continue with account activation. Once again, Spring to the rescue. We used Spring Retry one more time, this time to poll a couple of times with pause in between until the email arrives to EmailRepository. These parameters are configurable, we started with 10 attempts which was robust enough and 1 second between the attempts in order not to slow down the functional tests that much.
After all this was done, we only needed to parse email content, extract activation URL and activate account. With that last piece in place we had a fully functional sign up flow with activation which simulated user interaction and verified that email was sent with the right activation link.
You can see an example of our Cucumber scenario, with all of the above happening during the second step – receiving of email, polling for changes and sending the activation token.
Scenario: Sign up – happy path
When a new customer signs up
And customer’s email is verified
Then the customer is able to sign in with activated account
Testing a flow where you have email communication is hard, especially when you have expected actions taking place hidden in the email content. A perfect example is email activation as stated above. An alternative would be to activate account directly through functional testing application, but that would imply access to Database and changing the user journey, which would be just a part of the test. This way we achieved almost the same flow as the user would take to sign up and activate account with minimal changes in our application.