src / __tests__ / smtp.unit.test.ts
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
sendMail: vi.fn(),
verify: vi.fn(),
close: vi.fn(),
createTransport: vi.fn(),
}));
vi.mock("nodemailer", () => ({
default: {
createTransport: mocks.createTransport,
},
}));
import { sendEmail, verifySmtpConnection } from "../smtp";
const config = {
host: "smtp.example.com",
port: 587,
user: "user@example.com",
password: "app-password",
};
describe("SMTP transport lifecycle", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.createTransport.mockReturnValue({
sendMail: mocks.sendMail,
verify: mocks.verify,
close: mocks.close,
});
});
it("sends the expected plain-text fields and closes the transport", async () => {
mocks.sendMail.mockResolvedValue({ messageId: "<message@example.com>" });
await expect(
sendEmail(config, {
from: "user@example.com",
to: "recipient@example.com",
cc: "copy@example.com",
subject: "Subject",
body: "Body",
inReplyTo: "<original@example.com>",
references: "<original@example.com>",
}),
).resolves.toBe("<message@example.com>");
expect(mocks.sendMail).toHaveBeenCalledWith({
from: "user@example.com",
to: "recipient@example.com",
cc: "copy@example.com",
subject: "Subject",
text: "Body",
inReplyTo: "<original@example.com>",
references: "<original@example.com>",
});
expect(mocks.close).toHaveBeenCalledOnce();
});
it("closes the transport when sending fails", async () => {
mocks.sendMail.mockRejectedValue(new Error("SMTP failure"));
await expect(
sendEmail(config, {
from: "user@example.com",
to: "recipient@example.com",
subject: "Subject",
body: "Body",
}),
).rejects.toThrow("SMTP failure");
expect(mocks.close).toHaveBeenCalledOnce();
});
it("verifies and closes a transport", async () => {
mocks.verify.mockResolvedValue(true);
await expect(verifySmtpConnection(config)).resolves.toBeUndefined();
expect(mocks.verify).toHaveBeenCalledOnce();
expect(mocks.close).toHaveBeenCalledOnce();
});
});
src / __tests__ / smtp.unit.test.ts
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
sendMail: vi.fn(),
verify: vi.fn(),
close: vi.fn(),
createTransport: vi.fn(),
}));
vi.mock("nodemailer", () => ({
default: {
createTransport: mocks.createTransport,
},
}));
import { sendEmail, verifySmtpConnection } from "../smtp";
const config = {
host: "smtp.example.com",
port: 587,
user: "user@example.com",
password: "app-password",
};
describe("SMTP transport lifecycle", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.createTransport.mockReturnValue({
sendMail: mocks.sendMail,
verify: mocks.verify,
close: mocks.close,
});
});
it("sends the expected plain-text fields and closes the transport", async () => {
mocks.sendMail.mockResolvedValue({ messageId: "<message@example.com>" });
await expect(
sendEmail(config, {
from: "user@example.com",
to: "recipient@example.com",
cc: "copy@example.com",
subject: "Subject",
body: "Body",
inReplyTo: "<original@example.com>",
references: "<original@example.com>",
}),
).resolves.toBe("<message@example.com>");
expect(mocks.sendMail).toHaveBeenCalledWith({
from: "user@example.com",
to: "recipient@example.com",
cc: "copy@example.com",
subject: "Subject",
text: "Body",
inReplyTo: "<original@example.com>",
references: "<original@example.com>",
});
expect(mocks.close).toHaveBeenCalledOnce();
});
it("closes the transport when sending fails", async () => {
mocks.sendMail.mockRejectedValue(new Error("SMTP failure"));
await expect(
sendEmail(config, {
from: "user@example.com",
to: "recipient@example.com",
subject: "Subject",
body: "Body",
}),
).rejects.toThrow("SMTP failure");
expect(mocks.close).toHaveBeenCalledOnce();
});
it("verifies and closes a transport", async () => {
mocks.verify.mockResolvedValue(true);
await expect(verifySmtpConnection(config)).resolves.toBeUndefined();
expect(mocks.verify).toHaveBeenCalledOnce();
expect(mocks.close).toHaveBeenCalledOnce();
});
});