How to Test Received Emails with Cypress, Ethereal, and ImapFlow

Ashwani Kumar Jha's avatar

Ashwani Kumar Jha

Introduction

In modern web applications, email functionality plays a crucial role in user registration, password reset, notifications, and more. Verifying that emails are sent correctly and their content is accurate is an important aspect of testing. In this tutorial, we'll explore how to test received emails using Cypress, Ethereal, and ImapFlow.

Prerequisites

Before we dive into the implementation details, make sure we have the setup in place:

  1. Node.js and npm installed on the machine.
  2. A Cypress project set up in the preferred directory.

We'll cover the following topics in this article:

  1. What is Ethereal?
  2. What is ImapFlow?
  3. Setting up Ethereal for testing emails.
  4. Installing the required dependencies.
  5. Writing the email testing code in Cypress.
  6. Running the email tests.

What is Ethereal?

Ethereal is a service provided by Nodemailer that allows us to create and test email functionality without using a real email server. It generates a temporary SMTP server and provides us with an SMTP account to send and receive emails. Ethereal captures the sent emails, allowing us to view their content, including headers, body, and attachments.

What is ImapFlow?

ImapFlow is a JavaScript library for interacting with IMAP servers. IMAP (Internet Message Access Protocol) is a standard protocol used by email clients to retrieve emails from a mail server. ImapFlow provides an easy-to-use API to connect to an IMAP server, fetch emails, and perform various operations such as searching, filtering, and parsing email content.

Setting up Ethereal for Testing Emails

To begin, we need to set up an Ethereal account to send and receive emails during testing. Follow these steps:

  1. Visit the Ethereal website at https://ethereal.email/.
  2. Create an account
  3. Make note of the provided SMTP credentials, including the host, port, username, and password.

Installing the Required Dependencies

To work with Ethereal and ImapFlow in Cypress, we need to install the necessary dependencies. Let's open the terminal and navigate to the Cypress project's root directory. Then, run the following command:

npm install --save-dev nodemailer imapflow mailparser

These dependencies include Nodemailer for sending emails, ImapFlow for interacting with the IMAP server, and Mailparser for parsing email content.

Writing the code

  • Creating the lastEmail Plugin

In order to fetch the last received email in our Cypress tests, we'll create a custom Cypress plugin called lastEmail. This plugin will leverage imapflow and mailparser to retrieve and parse emails.

Create a new file named lastEmail.js inside the plugins directory of the Cypress project and add the following code:

const { ImapFlow } = require('imapflow');
const simpleParser = require('mailparser').simpleParser;
 
// Function to connect to the mailbox using Ethereal SMTP server details.
const connectToMailbox = async () => {
  // Replace these with the Ethereal SMTP server details obtained while creating the account.
  const client = new ImapFlow({
    host: 'smtp.ethereal.email',
    port: 'ethereal-port',
    secure: false,
    auth: {
      user: 'ethereal-username', // Ethereal username
      pass: 'ethereal-password', // Ethereal password
    },
  });
 
  await client.connect(); // Connect to the mailbox
  return client;
};
 
// Function to fetch the last email from the INBOX mailbox.
const fetchLastEmail = async (client) => {
  let lock;
  let message;
 
  try {
    lock = await client.getMailboxLock('INBOX'); // Acquire the mailbox lock
    message = await client.fetchOne(client.mailbox.exists, { source: true }); // Fetch the last email
  } finally {
    if (lock) {
      await lock.release(); // Release the mailbox lock
    }
  }
 
  return message;
};
 
// Function to parse the email content.
const parseEmail = async (source) => {
  const parsedEmail = await simpleParser(source);
 
  // Extract the necessary information from the parsed email
  return {
    subject: parsedEmail.subject,
    text: parsedEmail.text,
    html: parsedEmail.html,
    attachments: parsedEmail.attachments,
  };
};
 
// Function to retrieve the last received email from the mailbox.
const lastEmail = async () => {
  try {
    const client = await connectToMailbox();
    const message = await fetchLastEmail(client);
 
    if (!message) {
      throw new Error('No message found');
    }
 
    const source = Buffer.from(message.source);
    const parsedData = await parseEmail(source);
 
    await client.logout();
 
    return parsedData; // Return the parsed email data
  } catch (error) {
    console.log('Error:', error);
    throw error;
  }
};
 
export default lastEmail;
  • Creating a sendEmail plugin

During the test case, we need to simulate the process of sending an email from our application, so that we can test we can validate the email received using Ethereal and ImapFlow.

Create a new file named sendEmail.js inside the plugins directory of our Cypress project and add the following code:

const nodemailer = require('nodemailer');
 
const sendEmail = (options) => {
  const transporter = nodemailer.createTransport({
    host: 'smtp.ethereal.email',
    port: 'etheral-port',
    auth: {
      user: 'ethereal-email',
      pass: 'ethereal-password',
    },
  });
 
  return transporter.sendMail(options);
};
 
export default sendEmail;
  • Registering the task with Cypress

Next we need to update the cypress.config.js file. The setupNodeEvents function is responsible for registering the task with Cypress. This will make lastEmail and sendEmal accessible across all our tests without the need for duplicating code or configurations.

import { defineConfig } from 'cypress';
import lastEmail from './cypress/plugins/lastEmail';
import sendEmail from './cypress/plugins/sendEmail';
 
const setupNodeEvents = async (on) => {
  on('task', {
    async lastEmail() {
      const emailResponse = await lastEmail();
      return emailResponse;
    },
  });
  on('task', {
    async sendEmail(options) {
      await sendEmail(options);
      return null;
    },
  });
};
 
module.exports = defineConfig({
  e2e: {
    defaultCommandTimeout: 100000,
    video: false,
    setupNodeEvents,
  },
});

With this We should be able to use the lastEmail task in our Cypress tests by calling cy.task('lastEmail').

Let's write our Email Testing Code

We can now write the email testing code using Cypress. Let's create a new file called email.spec.js.

describe('Email Testing', () => {
  before(() => {
    // Delete the existing user or perform any necessary setup
  });
 
  it('Should send and receive an email', () => {
    // Send the email
    cy.task('sendEmail', {
      from: 'sender@example.com',
      to: 'ethereal-email',
      subject: 'Test Email',
      text: 'This is a test email.',
    }).then(() => {
      // Fetch the last received email
      cy.task('lastEmail').then((email) => {
        // Perform assertions on the received email
        expect(email.subject).to.equal('Test Email');
        expect(email.text).to.equal('This is a test email');
        // Add more assertions as needed
 
        // Log the email content
        cy.log('Received Email:', email);
      });
    });
  });
});

In this example, we have added a single test case that sends an email and then fetches the last received email. We can perform assertions on the email's subject, text, and other properties to validate its content. Finally, we log the email content for visibility.

Running the Email Tests

To run the email tests, lets open the terminal and navigate to the Cypress project's root directory. Then, run the following command:

npx cypress open

This command opens the Cypress Test Runner. From the Test Runner, click on the email.spec.js file to run the email tests.

Conclusion

In this article, we explored how to test received emails using Cypress, Ethereal, and ImapFlow.

During the test case, we simulate the process of sending an email from our application and validate its content by accessing the received email through Ethereal and ImapFlow. This allows us to thoroughly test our email functionality and ensure that the emails generated by our application meet our expectations. By automating email testing with Cypress, we can streamline our testing process and improve the overall quality of our applications.

Happy email testing with Cypress!

Last but not least, I will like to specially mention an error that I faced while writing this code.

I initially tried to write the code to fetch the email directly inside the test file, but encountered a Webpack compilation error.

The error occurred because ImapFlow is a server-side module and does not support being bundled by Webpack, which is primarily used for client-side code bundling.

To address this issue, we modified our approach and leveraged the plugins and setupNodeEvents configuration to include the ImapFlow-related code without going through the Webpack compilation process.

By writing the code as a plugin and defining the setupNodeEvents configuration, we can directly include the ImapFlow code in our tests without involving Webpack. This allows us to access the required ImapFlow functionality within our Cypress environment seamlessly.

Thank You!