nodemailer-mock
v2.1.0
Published
Easy as pie nodemailer mock for unit testing your Node.js applications.
Maintainers
Readme
nodemailer-mock
Easy as pie nodemailer mock for unit testing your Node.js applications.
Changelog · Contributing · Release runplan
This release supports Nodemailer 6, 7, 8, 9, and 10, as declared in
package.json's peerDependencies. Use a Node.js version supported
by your installed Nodemailer version. Nodemailer 10 bundles TypeScript declarations;
for supported versions without bundled declarations, install a matching
@types/nodemailer version in TypeScript projects.
The exported MockSentMessageInfo type describes the mock's result, including its
placeholder envelope and the Error returned in info.response for failed sends.
For explicit mock transporter annotations, use NodemailerMockTransporter or
Transporter<MockSentMessageInfo> to preserve the mock's result type. Standard
Nodemailer plugins and failure checks can be passed to the mock.
install
Nodemailer is a peer dependency. If your application already installs it, keep its
existing dependency and add only nodemailer-mock as a development dependency. For
a new test project, install both:
npm install nodemailer nodemailer-mock --save-devpnpm add -D nodemailer nodemailer-mockyarn add -D nodemailer nodemailer-mockmodule loading
Use require('nodemailer-mock') in CommonJS or
import nodemailermock from 'nodemailer-mock' in ESM. The default mock is shared
within the loaded module. Reset it between tests or create an independent mock:
const { getMockFor } = require('nodemailer-mock');
const nodemailermock = getMockFor(require('nodemailer'));getMockFor(nodemailer) returns a new mock with its own state and uses the supplied
Nodemailer module to create underlying transports. Replace nodemailer before
loading application code that imports it. With Jest, inject the real module from
jest.requireActual('nodemailer') in the manual mock shown below.
mock api
Use with test suites like jest, mocha, vitest, and node:test. There are some special methods available on the mocked module to help with testing. They are under the .mock key of the mocked nodemailer.
createTransport(transportOptions?, defaults?)
Returns a mocked Nodemailer transporter. You can omit the options, supply a
transport/options object, or use an SMTP URL. The second argument provides message
defaults; values passed to sendMail() override them.
const nodemailermock = require('nodemailer-mock');
const transport = nodemailermock.createTransport({}, {
from: '[email protected]',
subject: 'Default subject',
});sendMail() supports callbacks and Promises and records successful messages without
sending them through the underlying transport. Returned info values are mock
placeholders, including envelope, accepted, rejected, and pending; they do
not report actual recipient delivery. Inspect mock.getSentMail() for message
content. A failed callback may receive mock result information, while the Promise
rejects with the error.
verify() and close() are mocked by default. Setting setMockedVerify(false) or
setMockedClose(false) delegates to the underlying transport when one is available.
Real verification can connect to the configured mail server.
NodemailerMock.mock functions
reset: () => void- clears sent mail and the aggregate close count; restores failure settings, responses, mocked verify/close behavior, and disabled user plugins
- cancels pending idle changes scheduled with
scheduleIsIdle() - retains created transporters, their registered plugins, their individual close counts, and their current idle states
getTransporters: () => NodemailerMockTransporter[]- returns the transporters created by this mock, including those created before
reset()
- returns the transporters created by this mock, including those created before
getSentMail: () => Mail.Options[]- returns successfully sent message options, including applied defaults and plugin changes, since the last reset
getCloseCallCount: () => number- get the number of times the
transporter.close()was called across all instances
- get the number of times the
setShouldFailOnce: (isSet?: boolean) => void- sets a one-time failure for the next mocked send or verification; defaults to
truewhen omitted - passing
falseclears the one-time and persistent failure flags
- sets a one-time failure for the next mocked send or verification; defaults to
isShouldFailOnce: () => boolean- returns status of
setShouldFailOnce(?)
- returns status of
setShouldFail: (isFail: boolean) => void- indicate if errors should be returned for subsequent mocked send or verification calls
trueenables forced failuresfalseclears forced and one-time failures; per-message checks still apply
- indicate if errors should be returned for subsequent mocked send or verification calls
isShouldFail: () => boolean- returns status of
setShouldFail()
- returns status of
setShouldFailCheck: (check: CheckMailMessageOrNull) => void- indicate if the specific email passed to the function should fail the call to
transporter.sendMail()ortransport.send()- if function returns
true, return error - if function returns
false, the normal failure flags still apply
- if function returns
- receives a Nodemailer mail message; inspect fields such as
mail.data.to - runs before the normal failure flags; a failure here does not consume a one-time failure
- pass
nullto clear the check; it is not called byverify() - use the exported
CheckMailMessageOrNulltype; checks may use standard NodemailerMailMessagetypes - thrown
Errorobjects are returned unchanged; other thrown values are wrapped in anErrorwith the originalcause
- indicate if the specific email passed to the function should fail the call to
getShouldFailCheck: () => CheckMailMessageOrNull- returns the function used to check the
MailMessageornullif it is not set
- returns the function used to check the
setMockedVerify: (isMocked: boolean) => void- determine if a call to
transport.verify()should be mocked or passed through tonodemailer, defaults totrue.- if
true, return a mocked callback or Promise result - if
false, pass through to a realnodemailertransport
- if
- determine if a call to
isMockedVerify: () => boolean- returns status of
setMockedVerify(?)
- returns status of
setMockedClose: (isMocked: boolean) => void- determine if calls to
transporter.close()should be passed through to the underlying transport, defaults totrue.
- determine if calls to
isMockedClose: () => boolean- when the result is
truethe underlying transport is not used, whenfalsethe call is passed through.
- when the result is
setSuccessResponse: (response: string) => void- set the success message that is returned in the callback for
transporter.sendMail()ortransport.send()
- set the success message that is returned in the callback for
getSuccessResponse: () => string- returns the success message value
setFailResponse: (error: Error) => void- set the
Errorthat is returned in the callback fortransporter.sendMail()ortransport.send()
- set the
getFailResponse: () => Error- returns the fail
Errorvalue
- returns the fail
scheduleIsIdle: (isIdle: boolean, timeout: number) => void- after
timeoutmilliseconds, update the idle state of all transporters created by this mock
- after
setIsIdle: (isIdle: boolean) => void- update the idle state of all currently created transporters;
truealso emits theiridleevents
- update the idle state of all currently created transporters;
setUnmockedUsePlugins: (isUnmockUsePlugins: boolean) => voiddefaultfalse- run registered user plugins when
true; skip their execution whenfalse - Nodemailer's built-in processing still runs
- checked when each plugin runs, so it also applies to plugins already registered;
reset()disables them
- run registered user plugins when
isUnmockedUsePlugins: () => boolean- returns the status of
setUnmockedUsePlugins(?)
- returns the status of
NodemailerMockTransporter.mock functions
getPlugins: () => { [key: string]: NodemailerMockPlugin[] }- returns the plugins that have been added via
transporter.use()as arrays, keyed by step
- returns the plugins that have been added via
getCloseCallCount: () => number- get the number of times
close()has been called on this transporter. this number is not reset with the mock.
- get the number of times
setIdle(isIdle: boolean): void- sets the idle state of
transporter.isIdle()and emits anidleevent when theisIdleargument istrue.
- sets the idle state of
usage
The mocked module behaves in a similar fashion to other transports provided by nodemailer.
setup test
const nodemailermock = require('nodemailer-mock');
const transport = nodemailermock.createTransport();
// the email you want to send
const email = {
from: '[email protected]',
to: '[email protected]',
subject: 'Test message',
text: 'Hello from a test',
};use nodestyle callbacks
// send with nodestyle callback
transport.sendMail(email, function(err, info) {
if (err) {
return console.log('Error!', err, info);
}
return console.log('Success!', info);
});
// verify with nodestyle callback
transport.verify(function(err, success) {
if (err) {
return console.log('Error!', err);
}
return console.log('Success!', success);
});use promises
// send with promises
transport.sendMail(email)
.then(function(info) {
console.log('Success!', info);
})
.catch(function(err) {
console.log('Error!', err);
});
// verify with promises
transport.verify()
.then(function(success) {
console.log('Success!', success);
})
.catch(function(err) {
console.log('Error!', err);
});use async/await
Use this inside an async function after the setup above.
// send an email with async/await
try {
const info = await transport.sendMail(email);
console.log("Success!", info);
} catch (err) {
console.log("Error!", err);
}
// verify with async/await
try {
const success = await transport.verify();
console.log("Success!", success);
} catch (err) {
console.log("Error!", err);
}example tests using mocked module
The examples directory
contains runnable Jest, Mocha, Vitest, and node:test tests. It is available in the
repository and excluded from the npm package. From a repository checkout, run
pnpm install --frozen-lockfile, then pnpm run examples to run all four runners.
Individual commands are pnpm run example:jest, pnpm run example:mocha,
pnpm run example:node-test, and pnpm run example:vitest.
The Jest, Mocha, and node:test snippets below use this CommonJS application module
at the project root. The Vitest example mocks ESM imports directly.
// ./my-module.js
const nodemailer = require('nodemailer');
exports.sendEmail = async () => {
const transport = nodemailer.createTransport();
return transport.sendMail({
from: '[email protected]',
to: '[email protected]',
subject: 'Test message',
text: 'Hello from a test',
});
};example using jest
For a CommonJS Jest project, create ./__mocks__/nodemailer.js beside
node_modules, within Jest's configured roots:
// ./__mocks__/nodemailer.js
const nodemailer = jest.requireActual('nodemailer');
module.exports = require('nodemailer-mock').getMockFor(nodemailer);Jest automatically uses this manual mock for the nodemailer package. Load the
application in the test and reset the mock's state between tests:
// ./__tests__/my-test.js
const { mock } = require('nodemailer');
const { sendEmail } = require('../my-module');
beforeEach(() => mock.reset());
test('Sends an email using mocked Nodemailer', async () => {
await sendEmail();
expect(mock.getSentMail()).toHaveLength(1);
expect(mock.getSentMail()[0].to).toBe('[email protected]');
});With Jest configured to transform TypeScript to CommonJS, the equivalent test uses
a type assertion to access the mock helpers. The real Nodemailer type has no
mock property:
// ./__tests__/my-test.ts
import { beforeEach, expect, test } from '@jest/globals';
import * as nodemailer from 'nodemailer';
import type { NodemailerMock } from 'nodemailer-mock';
const { mock } = nodemailer as unknown as NodemailerMock;
const { sendEmail } = require('../my-module');
beforeEach(() => mock.reset());
test('Sends an email using mocked Nodemailer and TypeScript', async () => {
await sendEmail();
expect(mock.getSentMail()).toHaveLength(1);
expect(mock.getSentMail()[0].to).toBe('[email protected]');
});example using mocha
This CommonJS example temporarily intercepts require('nodemailer'). Install the
hook before loading the application, clear its cached module, and restore the
loader after the suite. This hook does not intercept native ESM imports.
// ./test/my-test.js
const assert = require('node:assert/strict');
const Module = require('node:module');
const nodemailermock = require('nodemailer-mock');
const originalLoad = Module._load;
const applicationPath = require.resolve('../my-module');
let app;
describe('Tests that send email', () => {
before(() => {
Module._load = function (request, parent, isMain) {
if (request === 'nodemailer') return nodemailermock;
return originalLoad.call(this, request, parent, isMain);
};
delete require.cache[applicationPath];
app = require(applicationPath);
});
beforeEach(() => nodemailermock.mock.reset());
after(() => {
Module._load = originalLoad;
delete require.cache[applicationPath];
nodemailermock.mock.reset();
});
it('records a successful email', async () => {
await app.sendEmail();
const sentMail = nodemailermock.mock.getSentMail();
assert.equal(sentMail.length, 1);
assert.equal(sentMail[0].to, '[email protected]');
});
it('rejects a failed email without recording it', async () => {
const error = new Error('My custom error');
nodemailermock.mock.setShouldFailOnce();
nodemailermock.mock.setFailResponse(error);
await assert.rejects(app.sendEmail(), (received) => received === error);
assert.equal(nodemailermock.mock.getSentMail().length, 0);
});
});example using vitest
To mock nodemailer using vitest, use vi.mock() to replace nodemailer with nodemailer-mock:
/**
* Vitest Test
* ./test/my-test.test.mjs
**/
import { describe, it, beforeEach, expect, vi } from "vitest";
import nodemailermock from "nodemailer-mock";
// Mock nodemailer with nodemailer-mock
vi.mock("nodemailer", () => {
return import("nodemailer-mock");
});
describe("Tests that send email", () => {
beforeEach(() => {
nodemailermock.mock.reset();
});
it("should send an email using nodemailer-mock", async () => {
// import the module under test after mocking
const nodemailer = await import("nodemailer");
const transport = nodemailer.createTransport({});
await transport.sendMail({ to: "[email protected]", from: "[email protected]" });
// check the mock for our sent emails
const sentEmails = nodemailermock.mock.getSentMail();
// there should be one
expect(sentEmails.length).toBe(1);
// and it should match the to address
expect(sentEmails[0].to).toBe("[email protected]");
});
});example using node:test
Node.js's built-in test runner, node:test,
supports module mocking with the --experimental-test-module-mocks flag. The
example selects mock options based on the Node.js version. See the
CI matrix
for the versions tested by this project, and choose a version supported by your
installed Nodemailer release.
/**
* node:test Test
* ./test/my-test.mjs
*
* Run with: node --experimental-test-module-mocks --test ./test/my-test.mjs
**/
import { describe, it, beforeEach, mock } from "node:test";
import assert from "node:assert/strict";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
// Register the mock *before* importing modules that use nodemailer
const nodemailermock = await import("nodemailer-mock");
const namedExports = Object.fromEntries(
Object.entries(nodemailermock).filter(
([name]) => !["default", "__esModule", "module.exports"].includes(name),
),
);
const moduleMockOptions = Number.parseInt(process.versions.node, 10) >= 24
? { exports: { ...namedExports, default: { ...namedExports } } }
: { namedExports, defaultExport: { ...namedExports } };
// CommonJS and ESM can resolve to different entry points. Mock each one.
const require = createRequire(import.meta.url);
const entries = [
import.meta.resolve("nodemailer"),
pathToFileURL(require.resolve("nodemailer")).href,
];
for (const entry of new Set(entries)) {
mock.module(entry, moduleMockOptions);
}
// Now import the module under test
const myModule = await import("../my-module.js");
describe("Tests that send email", () => {
beforeEach(() => {
nodemailermock.mock.reset();
});
it("should send an email using nodemailer-mock", async () => {
await myModule.sendEmail();
// check the mock for our sent emails
const sentEmails = nodemailermock.mock.getSentMail();
// there should be one
assert.equal(sentEmails.length, 1);
// and it should match the to address
assert.equal(sentEmails[0].to, "[email protected]");
});
});