Skip to content
Justin SilverTECHNOLOGY, TRAVEL & EVERYTHING IN BETWEEN

Programming

Unit testing email with nodemailer-mock

August 17, 2016

Paper envelopes and message cards captured in a glass inbox, with a green check mark on one card.

I needed to unit test a web application that sent email. I wanted to check that it was sending the right message to the right person, without sending real emails every time I ran the test suite.

I created nodemailer-mock to do this. It replaces Nodemailer in your tests and records the messages your application sends. You can inspect those messages, make assertions about their contents, and simulate failures to check your error handling.

Testing the message

Checking that sendMail() was called only gets you part of the way. A test should also catch a wrong recipient or a missing subject. With nodemailer-mock, the messages are available as ordinary JavaScript objects, so you can check the fields that matter to your application.

There is no SMTP server to configure for these unit tests, and no inbox to check after each run. The mock keeps the messages in memory.

Installation

For an application using Nodemailer, install the mock and the tools used in this example:

npm install nodemailer
npm install --save-dev nodemailer-mock mocha proxyquire

A unit test for a welcome email

Here is a simple application module, mailer.js, that sends a welcome email. Replace the example SMTP settings with your application's configuration when using it outside the tests.

const nodemailer = require('nodemailer');

const transport = nodemailer.createTransport({
  host: 'smtp.example.test',
  port: 587,
});

module.exports = function sendWelcomeEmail(address, callback) {
  return transport.sendMail({
    from: 'no-reply@example.test',
    to: address,
    subject: 'Welcome!',
    text: 'Thanks for signing up.',
  }, callback);
};

In test/mailer.test.js, Proxyquire loads the application module with nodemailer-mock in place of Nodemailer. This lets the test call the application's email function while the mock captures the message.

The examples below use the current package API.

const assert = require('assert');
const proxyquire = require('proxyquire').noCallThru();
const nodemailerMock = require('nodemailer-mock');

const sendWelcomeEmail = proxyquire('../mailer', {
  nodemailer: nodemailerMock,
});

describe('Welcome email', function () {
  beforeEach(function () {
    nodemailerMock.mock.reset();
  });

  it('sends the expected message', function (done) {
    sendWelcomeEmail('reader@example.test', function (err) {
      if (err) return done(err);

      const sent = nodemailerMock.mock.getSentMail();
      assert.strictEqual(sent.length, 1);
      assert.strictEqual(sent[0].from, 'no-reply@example.test');
      assert.strictEqual(sent[0].to, 'reader@example.test');
      assert.strictEqual(sent[0].subject, 'Welcome!');
      assert.strictEqual(sent[0].text, 'Thanks for signing up.');
      done();
    });
  });

  it('returns an error when sending fails', function (done) {
    const expected = new Error('Email send failed');
    nodemailerMock.mock.setFailResponse(expected);
    nodemailerMock.mock.setShouldFailOnce();

    sendWelcomeEmail('reader@example.test', function (err) {
      assert.strictEqual(err, expected);
      assert.strictEqual(nodemailerMock.mock.getSentMail().length, 0);
      done();
    });
  });
});

Run the tests with:

npx mocha

The first test checks that the application sent exactly one message with the expected sender, recipient, subject, and body. Calling mock.reset() before each test clears the recorded mail and resets the failure settings so one test does not affect the next.

Testing send failures

The second test tells the mock to fail the next send and checks that the application's email function returns the error to its caller. You can use the same setup to test a route's error response or an application's retry logic.

This is useful because you can exercise those failure paths without changing SMTP credentials or waiting for a mail server to be unavailable.

These tests check what your application hands to Nodemailer and how it handles the result. Actual delivery and email-client rendering belong in separate integration tests.

You can find nodemailer-mock on npm, with the source and more examples on GitHub.