Mock smtplib to Test Patch Email Series in Python

Simulate sending a numbered series of patch emails with smtplib and verify the calls using unittest.mock without a real mail server.

Medium Python 3.9+ Aug 9, 2026 Git + Python 13 views 0 copies

Python code

35 lines
Python 3.9+
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from unittest.mock import patch, Mock

def send_patch_series(subject_prefix, patches, smtp_host="localhost", smtp_port=25):
    """Simulate sending a series of patch emails."""
    for i, patch_content in enumerate(patches, start=1):
        msg = MIMEMultipart()
        msg["From"] = "sender@example.com"
        msg["To"] = "recipient@example.com"
        msg["Subject"] = f"{subject_prefix} [{i}/{len(patches)}]"
        msg.attach(MIMEText(patch_content, "plain"))

        with smtplib.SMTP(smtp_host, smtp_port) as server:
            server.send_message(msg)
            print(f"Sent: {msg['Subject']}")

def demo():
    patches = [
        "diff --git a/file1.py b/file1.py\n+def new_function():",
        "diff --git a/file2.py b/file2.py\n+class NewClass:",
        "diff --git a/file3.py b/file3.py\n+import new_module",
    ]

    fake_server = Mock()

    with patch("smtplib.SMTP", return_value=fake_server) as mock_smtp:
        send_patch_series("PATCH: feature-x", patches)

    print("\nSMTP called:", mock_smtp.call_count, "time(s)")
    print("send_message calls:", fake_server.send_message.call_count)

if __name__ == "__main__":
    demo()

Output

stdout
Sent: PATCH: feature-x [1/3]
Sent: PATCH: feature-x [2/3]
Sent: PATCH: feature-x [3/3]

SMTP called: 3 time(s)
send_message calls: 3

How it works

The patch("smtplib.SMTP", return_value=fake_server) context manager replaces the real SMTP constructor with a Mock object, so no network connection is made. Each loop iteration creates a MIMEMultipart message, attaches the patch diff as plain text, and opens a fake SMTP context through the mock. Calling server.send_message(msg) invokes the mock, which records the call for later assertions. The final prints confirm how many times SMTP was constructed and how many messages were sent, verifying the series was dispatched correctly.

Common mistakes

  • Forgetting to patch the full path `smtplib.SMTP` instead of just `SMTP` in the local scope
  • Not passing `return_value=fake_server`, which makes every context manager return a new Mock with no shared call count
  • Assuming real emails are sent when the mock is active — assert on the fake server, not on SMTP side effects

Variations

  1. Use `unittest.mock.patch` as a decorator above the test function for cleaner separation
  2. Replace the Mock with `unittest.mock.MagicMock` to also handle async methods or nested calls if needed

Real-world use cases

  • Testing a CI pipeline that emails patch series to maintainers without spamming a real mailbox.
  • Validating email subject formatting and recipient logic in code-review bots sending change notifications.
  • Simulating multi-email workflows in regression tests so local development needs no local SMTP server.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Git + Python

Related tutorials and quizzes for this topic.