E2E Testing of emails in mailhog using Cypress

Automate e2e testing of your email flow by extending Cypress using plugins and leveraging a fake SMTP server
01.02.2021
Kailaash Balachandran
Tags

End-to-end testing (E2E) is the process of loading up your web application in the browser and testing the app as if a real user were performing the actions such as clicking buttons, submitting forms, etc. It allows you to speed up the user experience testing process, and help you catch bugs that you didn’t even know existed. That said, sending emails is also a critical part of web applications. It often marks major checkpoints of the user journey such as signing up or completing a purchase.

Given the importance of emails, it is necessary to e2e test the functionality during development. This is when MailHog comes in handy as an email-testing tool with a fake SMTP server underneath. It encapsulates the SMTP protocol with extensions and does not require specific backend implementations. MailHog runs a super simple SMTP server that hogs outgoing emails sent to it. You can see the hogged emails in a web interface.

For the e2e tests, let’s leverage Cypress as it provides a clean DSL for writing expressive tests, and an electron app that provides time-travel debugging capabilities for each spec.

The goal of this tutorial is to demonstrate E2E Testing of emails in mailhog using Cypress. Now, let’s get our hands dirty.

Install Cypress

To start off, create a new folder in the project root folder, and initialize a new npm project:

mkdir e2e
cd e2e
npm init -y

Then install Cypress with:

npm install cypress --save-dev  

This installs Cypress as a dev dependency where the executable now exists on ./node_modules/.bin/.

To open Cypress, you can either run ./node_modules/.bin/cypress open

Or add Cypress commands to the scripts field in your package.json file.

{ 
  "scripts": { 
    "cypress:open": "cypress open" 
  } 
}

Blog post- E2E Testing of emails in mailhog using Cypress- Image cypress default (1)

Now you can invoke the command npm run cypress:open from your project and the Cypress electron app will open right up for you. The Cypress Test Runner manages to identify all the compatible browsers on the user’s computer. In the top right corner of the Test Runner is the drop-down to choose a different browser for your E2E tests.

Cypress installation also comes with several default integration tests under the _ /integration directory. Assuming Cypress has been set up successfully, you should be able to run the default integration tests from the electron app. If you want to learn more about the project structure and writing basic tests, visit the official Cypress docs for more information. Now, let’s proceed to the E2E email testing part of the tutorial.

Set up Mailhog

To setup Mailhog, navigate to the Cypress project folder and create a docker-compose.yml as below.

version: '3'

services:
 mailhog:
   image: mailhog/mailhog:latest
   ports:
     - '8025:8025'
     - '1025:1025'
   environment:
     MH_STORAGE: maildir
     MH_MAILDIR_PATH: /tmp
   volumes:
     - maildir:/tmp
   command: -invite-jim=1 -jim-accept=0.50
volumes:
 maildir: {}

The .yml config sets up Mailhog in a container and maps the port 1025 to SMTP server and 8025 to the web UI. To start the docker service, enter docker-compose up in the terminal. After a successful build, the web UI will be available on http://localhost:8025 as shown in image below.

Blog post- E2E Testing of emails in mailhog using Cypress- Image localhost (1)

For the sake of simplicity in this tutorial, let’s create a basic node script that sends a confirmation email upon user email verification. To do so, create a send_verification_email.js file in the project root and add the following:

const mailer = require('nodemailer');

const smtp = mailer.createTransport({
 host: 'localhost',
 port: '1025',
 auth: {
   user: 'user',
   pass: 'password',
 }
});

const mailOptions = {
 from: 'noreply@test.com',
 to: 'noreply@test.com',
 subject: 'Your account is now confirmed',
 html: '<h1>Thanks for the verification</h1><p>Your username is: johndoe</p>'
};

smtp.sendMail(mailOptions, function(err, info) {
 if (!err) {
   console.log('Mail success: ' + info.response);
 } else {
   console.log('Mail err', err);
 }
 smtp.close();
});

You will need ensure Mailhog is available on localhost:8025 and then run:

node send_verification_email.js

Upon successful operation, you should see:

>> node verification_email.js
Mail success: 250 Ok: queued as V45A1FjO-u_MKxyZEHpGRQLYC3x-shdebqeHnSEYFv4=@mailhog.example

And when you access http://localhost:8025, you will notice the inbox has an email with the subject “Your account is confirmed”.

Blog post- E2E Testing of emails in mailhog using Cypress- Image localhost (2)

Write Cypress Tests with Mailhog

Now that we have set up an email sending script and active Mailhog inbox, let’s add E2E email testing capabilities in Cypress. To do so, navigate to the Cypress root folder and install an npm package called cypress-mailhog.

npm install --dev cypress-mailhog

Import the package into your Cypress command file.

// cypress/support/commands.js
import 'cypress-mailhog';

Add the base url of your MailHog installation to your cypress.json:

{
  ...
  "mailHogUrl": "http://localhost:8025"
}

The E2E test that we will write, will go inside the integration folder. To do so, navigate to the /integration, create a file, mail.spec.js and add the following test:

context('User Onboarding Emails', () => {
 it('Verification email', () => {
     cy.mhGetMailsBySubject('Your account is now confirmed')
     .should('have.length', 1);
 })
})

This simple test uses cy.mhGetMailsBySubject() to check if the account verification email exists. We could also check if the email body contains specific text, in this case, a username.

 it('Email should contain username info', () => {
   const mail = cy.mhGetMailsBySubject('Your account is now   confirmed').mhFirst().mhGetBody();
   mail.should('contain', 'Your username is');
 })

The package also provides several commands for Mailhog email interaction that you could use to enhance tests. What more? Let’s write a Cypress test to check if attachments are sent in the email. For this, create send_verification_email_with_attachment.js in the project root and add the following.

const mailer = require('nodemailer');
const fs = require('fs');

const smtp = mailer.createTransport({
 host: '0.0.0.0',
 port: '1025',
 auth: {
   user: 'user',
   pass: 'password',
 }
});

const mailOptions = {
 from: 'noreply@test.com',
 to: 'johndoe@test.com',
 subject: 'Email sent with an image attachment',
 html: '<h1>This email contains an image attachment</h1>',
 attachments: [
   {
     filename: 'unsplash.jpg',
     content: fs.createReadStream('./files/unsplash.jpg'),
     contentType: 'image/jpeg'
   }
 ]
};

smtp.sendMail(mailOptions, function(err, info) {
 if (!err) {
   console.log('Mail success: ' + info.response);
 } else {
   console.log('Mail err', err);
 }
 smtp.close();
});

});

The above script sends an email with a simple .txt file. To run, type:

node send_verification_email_with_attachment.js  

On success, the Mailhog should’ve received the email with attachments. You can verify the same by visiting http://localhost:8025

Now, let’s add an E2E test to check if the email has the expected attachment, which is an image in our example. To do so, add the following to mail.spec.js.

 it('Email should contain an image attachment', () => {
   const attachmentContentType = 'image/jpeg; name=unsplash.jpg';
   cy.mhGetMailsBySubject('Email sent with an image attachment').mhFirst().then(mail => {
     const imageAttachment = mail.MIME.Parts.filter(mime => {
       const contentType = mime.Headers['Content-Type'] || null;
       if (contentType) {
         return contentType.includes(attachmentContentType);
       }
     })
     expect(imageAttachment).to.have.lengthOf(1)
   });
 })

The above test first finds the email with the subject line. The desired image is then found by filtering the mime data. Lastly, we check the length of the returned array, which has to be 1 if the image is found. This simple test snippet can be extended further to check for PDFs, Excel sheets, etc.

E2E tests are crucial for the long-term success of any web application, and Cypress offers a great deal for developers in achieving the same. This tutorial demonstrates E2E testing of emails in Mailhog using Cypress and its third party plugins. If you’re interested in learning more about extending Cypress using plugins, then be sure to head over to their official docs on the Cypress Plugin Ecosystem.


Header Image by Ferenc Almasi on Unsplash