في الجزء الأول من السلسلة وصلنا إلى قاعدة بسيطة: الكود يبقى كما هو، والخدمة الخارجية يحل محلها على جهازك بديل يتحدث بروتوكولها نفسه، ورأينا أن المتصفح يعامل localhost كسياق آمن دون شهادة، وأن الأسماء الأخرى تحتاج DNS محلياً وHTTPS. والسؤال الآن عملي: أي أداة تستخدم لكل خدمة؟ فلكل خدمة ثلاث أدوات مشهورة أو أكثر، وبعضها توقف تطويره منذ سنوات، وبعضها تغيرت رخصته أو أصبح يحتاج حساباً، وأغلب المقالات التي تقارنها كتبت قبل هذه التغييرات.
والمواضيع في السلسلة:
- بيئة التطوير المحلية: الفكرة، وما الذي تحاكيه، ولماذا.
- أدوات بيئة التطوير المحلية (الموضوع الحالي).
- التطبيق العملي: ملف Docker Compose واحد يجمع الأدوات التي نختارها هنا.
وسوف نناقش في هذا المقال ما يلي:
- صناديق التقاط البريد Mail Catchers: مقارنة بالقياس بين Mailpit وMailHog وsmtp4dev.
- شهادات HTTPS المحلية: mkcert وجهة الإصدار الداخلية في Caddy وstep-ca، وما يفعله Traefik، مع مدة كل شهادة كما قسناها.
- الأسماء المحلية:
*.localhostوخدمات nip.io وsslip.io وملف hosts وdnsmasq وCoreDNS. - تخزين S3 المحلي ومحاكيات السحابة، وما تغير في LocalStack في 2026.
- محاكاة ال API الخارجي: WireMock وMockoon وPrism.
- استقبال ال Webhooks على جهازك: أنفاق cloudflared وngrok، وخدمة smee.io.
- مكتبة Testcontainers ومواصفات Dev Containers، وأين يقف كل منهما من ملف Compose.
- جدول قرار يجمع كل ذلك.
والصورة التالية تبين الخريطة التي يسير عليها المقال:

صناديق التقاط البريد: Mailpit أم MailHog أم smtp4dev؟
شرحنا فكرة صندوق الالتقاط Mail Catcher في الجزء الأول، وهنا نقارن الأدوات الثلاث الأشهر بالأرقام. فقد شغلنا كل أداة في Container على شبكة Docker داخلية لا تصل إلى الإنترنت، وأرسلنا إلى كل منها 600 رسالة صغيرة على اتصال SMTP واحد بسكربت Python، ثم سألنا ال API عن عدد الرسائل المحفوظة، وقسنا الذاكرة بالأمر docker stats. والسكربت كما يلي:
# Sends N small messages to one SMTP mail catcher over a single connection and prints the time it took.
# python send.py <host> <port> <count>
import smtplib, sys, time
from email.message import EmailMessage
host, port, count = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
start = time.perf_counter()
with smtplib.SMTP(host, port, timeout=60) as s:
for i in range(count):
m = EmailMessage()
m["From"], m["To"], m["Subject"] = "[email protected]", f"user{i}@example.com", f"Order #{i} confirmed"
m.set_content(f"Hello user{i}, your order #{i} is confirmed.\n")
s.send_message(m)
print(f"{host}: {count} messages in {time.perf_counter() - start:.2f}s")docker network create --internal lab-ldev-tools
docker run -d --name lab-ldev-mailpit2 --network lab-ldev-tools axllent/mailpit:v1.31.5
docker run -d --name lab-ldev-smtp4dev --network lab-ldev-tools rnwood/smtp4dev:3.15.0
docker run -d --name lab-ldev-mailhog --network lab-ldev-tools mailhog/mailhog:v1.0.1
docker run --rm --name lab-ldev-sender --network lab-ldev-tools -v "$PWD:/lab:ro" python:3.13-alpine sh -c '
python /lab/send.py lab-ldev-mailpit2 1025 600
python /lab/send.py lab-ldev-mailhog 1025 600
python /lab/send.py lab-ldev-smtp4dev 25 600
wget -qO- "http://lab-ldev-mailpit2:8025/api/v1/messages?limit=1" | grep -o "\"total\":[0-9]*"
wget -qO- "http://lab-ldev-mailhog:8025/api/v2/messages?limit=1" | grep -o "\"total\":[0-9]*"
wget -qO- "http://lab-ldev-smtp4dev/api/messages?pageSize=1" | grep -o "\"rowCount\":[0-9]*"'والمخرج سوف يكون كما يلي:
lab-ldev-mailpit2: 600 messages in 48.83s
lab-ldev-mailhog: 600 messages in 7.57s
lab-ldev-smtp4dev: 600 messages in 178.11s
"total":500
"total":600
"rowCount":100والجدول التالي يجمع المخرج مع الذاكرة وحجم ال Image وحالة كل مشروع كما قرأناها على GitHub في 12 أكتوبر 2026:
| المقياس | Mailpit | smtp4dev | MailHog |
|---|---|---|---|
| الإصدار المختبر | الإصدار v1.31.5 في 11 أكتوبر 2026 | الإصدار 3.15.0 في مارس 2026 | الإصدار v1.0.1 في أغسطس 2020 |
| الرخصة | MIT | BSD-3-Clause | MIT |
| حالة التطوير | نشط | نشط | لا يوجد إصدار منذ 2020، وآخر تعديل في المستودع في فبراير 2024 |
حجم ال Image كما يعرضه docker images | 59.9MB | 588MB | 572MB |
| الذاكرة قبل الإرسال | 13MiB | 76MiB | 4.8MiB |
| الذاكرة بعد 600 رسالة | 22MiB | 177MiB | 4.8MiB |
| الرسائل المحفوظة من 600 | 500 | 100 | 600 |
| زمن إرسال 600 رسالة (للتوضيح فقط) | 48.8 ثانية | 178.1 ثانية | 7.6 ثانية |
في النتائج أعلاه لاحظ التالي:
- الأزمنة قيست على جهاز واحد كانت تعمل عليه في الوقت نفسه أعمال أخرى ثقيلة، فخذها للمقارنة بين الأدوات في هذه التجربة فقط، وليس كأداء متوقع على جهازك، والمهم منها أن الثلاث تستقبل مئات الرسائل في دقائق على الأكثر، وهذا أكثر مما يحتاجه أي اختبار.
- العدد المحفوظ هو أهم سطر في الجدول، فكل أداة تحذف القديم بحد افتراضي مختلف: Mailpit يحتفظ بآخر 500 رسالة بحسب الخيار
--maxفي خيارات التشغيل، وsmtp4dev يحتفظ بآخر 100 بحسبNumberOfMessagesToKeepفي ملف إعداده، وMailHog يحفظ كل شيء في الذاكرة حتى يتوقف. وهذا يعني أن اختبار حمل يرسل ألف رسالة ثم يبحث عن الأولى سوف يفشل في الأداتين الأوليين دون أي خطأ في تطبيقك. - أداة Mailpit تحفظ الرسائل افتراضياً في ملف مؤقت يحذف عندما يتوقف، فإذا أردت أن تبقى الرسائل بعد إعادة التشغيل فحدد له قاعدة بيانات بالخيار
--databaseفي Volume.
أداة Mailpit
أداة Mailpit برنامج واحد مكتوب بلغة Go، فيه سيرفر SMTP على المنفذ 1025 وواجهة ويب وAPI على المنفذ 8025، وهو الذي نستخدمه في تجارب هذا الموقع.
نقاط القوة: API كامل للبحث والقراءة والحذف تستخدمه الاختبارات الآلية، وميزة Chaos التي أضيفت في الإصدار 1.20.0 وتجعل سيرفر SMTP يرفض الرسائل برمز خطأ تختاره عند المرسل أو المستلم أو المصادقة لتختبر كيف يتصرف تطبيقك عندما يفشل البريد كما في توثيق Chaos، وإطلاق الرسالة Release إلى سيرفر حقيقي يدوياً أو تلقائياً مع قائمة عناوين محظورة كما في توثيق SMTP Relay، وفحص الروابط وتوافق HTML مع برامج البريد، وWebhook عند وصول رسالة، وصغر حجمه.
نقاط الضعف: الحد الافتراضي 500 رسالة، والتخزين المؤقت الذي يضيع عند إعادة التشغيل، وميزة Relay نفسها، فهي الطريق الوحيد الذي يخرج منه بريد حقيقي من بيئة التطوير، فلا تفعلها إلا لعناوين تملكها.
أداة smtp4dev
أداة smtp4dev مكتوبة ب .NET، وبدأت كأداة لنظام Windows ثم أصبحت تعمل في Docker على كل الأنظمة.
نقاط القوة: يقدم IMAP وPOP3 بجانب SMTP، فتستطيع أن تفتح الرسائل من برنامج بريد حقيقي مثل Thunderbird، ويدعم TLS والمصادقة في SMTP لتختبر إعداد الإنتاج نفسه، ويعرض تحذيرات عن محتوى الرسالة، ويحفظ الرسائل في قاعدة SQLite.
نقاط الضعف: أكبر الثلاثة في الحجم والذاكرة، وكان الأبطأ في تجربتنا، والحد الافتراضي 100 رسالة فقط.
أداة MailHog القديمة
أداة MailHog كانت الأداة الأشهر لسنوات، وتجدها في ملفات Compose ودروس كثيرة، وفيها ميزة اسمها Jim تقطع الاتصال وترفض الرسائل عشوائياً لاختبار الأعطال.
نقاط القوة: أقل الثلاثة استهلاكاً للذاكرة في تجربتنا، وأسرعها في الاستقبال، ولا يحذف الرسائل إلا عندما يتوقف.
نقاط الضعف: لم يصدر منه إصدار منذ أغسطس 2020، وأي مشكلة أمنية أو توافق مع مكتبات اليوم لن تصلح، والتخزين في الذاكرة يعني أن الرسائل تضيع مع إعادة التشغيل، وال API أبسط من API Mailpit. ولهذا لا ننصح باستخدامه في مشروع جديد، وإذا كان في ملفات مشروعك فاستبدال ال Image ب Mailpit يكفي غالباً لأن الاثنين يستخدمان المنفذين 1025 و8025.
شهادات HTTPS المحلية: من يصدرها، ولكم من الوقت؟
عندما تحتاج HTTPS على اسم ليس localhost تحتاج جهة إصدار محلية Local CA تصدر الشهادة، ثم تجعل المتصفح والأدوات تثق بها، وقد شرحنا الشهادات وسلسلة الثقة في مقال الشهادات الرقمية. والأدوات هنا تختلف في سؤالين: كم تعيش الشهادة، وماذا تفعل الأداة بمخزن الثقة Trust Store على جهازك؟ وقد أصدرنا من كل أداة شهادة في Container مؤقت وقرأنا تواريخها بالأمر openssl x509 أو step certificate inspect، والجدول التالي يبين النتائج:
| الأداة | الشهادة الرئيسية Root | الشهادة الوسيطة | شهادة الموقع | مخزن الثقة |
|---|---|---|---|---|
| mkcert v1.4.4 | 10 سنوات | لا توجد | سنتان وثلاثة أشهر | الأمر mkcert -install يضيفها إلى مخزن النظام وFirefox وJava |
Caddy 2.11.7 مع tls internal | قرابة 10 سنوات | 7 أيام | 12 ساعة | يحاول إضافتها إلى مخزن النظام تلقائياً إلا إذا كتبت skip_install_trust |
| step-ca 0.30.2 | 10 سنوات | 10 سنوات | 24 ساعة | لا يلمس جهازك، وأنت تقرر أين تضيفها |
أداة mkcert
أداة mkcert برنامج سطر أوامر واحد ينشئ جهة إصدار في مجلد على جهازك، ويصدر منها شهادة لأي أسماء تكتبها، ولا يشغل أي سيرفر. والمثال التالي يصدر شهادة لنطاق lab.test وكل ما تحته داخل Container مؤقت، فلا يدخل شيء إلى جهازك:
docker run --rm -e CAROOT=/ca -v "$PWD/ca:/ca" -v "$PWD/certs:/certs" -w /certs alpine:3.22 sh -c '
wget -q -O /usr/local/bin/mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64
chmod +x /usr/local/bin/mkcert
mkcert -cert-file lab.pem -key-file lab-key.pem "lab.test" "*.lab.test"'والمخرج سوف يكون كما يلي:
Created a new local CA 💥
Note: the local CA is not installed in the system trust store.
Run "mkcert -install" for certificates to be trusted automatically ⚠️
Created a new certificate valid for the following names 📜
- "lab.test"
- "*.lab.test"
Reminder: X.509 wildcards only go one level deep, so this won't match a.b.lab.test ℹ️
The certificate is at "lab.pem" and the key at "lab-key.pem" ✅
It will expire on 12 January 2029 🗓في المخرج أعلاه لاحظ التالي:
- لم نشغل
mkcert -install، فالشهادة صدرت لكن لا أحد يثق بها بعد، وفي الجزء الثالث نجعل متصفحاً داخل Container وحده يثق بها، دون أن نلمس جهازك. - التذكير بأن الشهادة الشاملة Wildcard تغطي مستوى واحداً فقط، فـ
*.lab.testلا تغطيapi.v2.lab.test، وسوف نرى الخطأ بعينه في الجزء الثالث. - عندما قرأنا الشهادة الرئيسية بالأمر
openssl x509 -ext basicConstraints,nameConstraintsوجدناCA:TRUE, pathlen:0ولم نجد أي قيود على الأسماء Name Constraints، وهذا يعني أن هذه الجهة تستطيع أن تصدر شهادة يقبلها جهازك لأي موقع في العالم، ولهذا يحذر التوثيق من مشاركة الملفrootCA-key.pem.
نقاط القوة: أبسط الأدوات، وأمر واحد يصدر الشهادة، ويعمل على Windows وmacOS وLinux، ويضيف الجهة إلى Firefox وJava بجانب النظام.
نقاط الضعف: آخر إصدار v1.4.4 في أبريل 2022 وآخر تعديل في المستودع في أغسطس 2024، والأداة تعمل ولا تحتاج تغييرات كثيرة لكن لا تتوقع ميزات جديدة، والشهادة تعيش أكثر من سنتين، وليس لديها ACME، فتنقل الملفات إلى ال Reverse Proxy بنفسك.
جهة الإصدار الداخلية في Caddy
سيرفر Caddy الذي شرحنا تثبيته في دليل Caddy يحمل جهة إصدار داخلية، فإذا كان اسم الموقع localhost أو ينتهي بـ .localhost أو .local أو .internal أو .home.arpa أو كان عنوان IP، يصدر له شهادة منها تلقائياً كما يشرح توثيق HTTPS التلقائي، ولأي اسم آخر تكتب tls internal. والإعداد الذي استخدمناه في التجربة:
{
skip_install_trust
}
app.localhost {
tls internal
respond "hi"
}وعند التشغيل كتب Caddy في السجل:
"logger":"pki.ca.local","msg":"root certificate trust store installation disabled; unconfigured clients may show warnings","path":"storage:pki/authorities/local/root.crt"ولاحظ هنا أن السلوك الافتراضي دون skip_install_trust هو أن يحاول Caddy إضافة جهته إلى مخزن الثقة في النظام، وقد يطلب كلمة المرور لذلك، فإذا شغلته مباشرة على جهازك وليس في Container فهذا يعني أن جهة إصدار جديدة دخلت مخزن جهازك دون أن تقصد.
نقاط القوة: لا توجد أداة إضافية، والشهادات تتجدد وحدها، وعمر الشهادة 12 ساعة يقلل أثر تسرب مفتاح الموقع.
نقاط الضعف: الشهادة داخل Caddy فقط، فالخدمات التي تحتاج ملف شهادة لها لا تجده بسهولة، والثقة بالجهة في المتصفحات والأدوات الأخرى عمل يدوي عندما يعمل Caddy في Container.
جهة الإصدار step-ca للفريق
مشروع step-ca جهة إصدار كاملة برخصة Apache-2.0 تعمل كسيرفر، وتصدر الشهادات عبر عدة طرق Provisioners منها ACME، أي البروتوكول نفسه الذي تستخدمه Let's Encrypt، وبالتالي يستطيع Caddy أو Traefik أو certbot أن يطلبوا منها الشهادات كما يطلبونها في الإنتاج. وقد أصدرنا منها شهادة لاسم app.lab.test، والمخرج كما يلي:
X.509v3 TLS Certificate (ECDSA P-256) [Serial: 6991...9916]
Subject: app.lab.test
Issuer: lab-ldev Intermediate CA
Provisioner: admin [ID: 5eup...61j4]
Valid from: 2026-10-12T11:34:45Z
to: 2026-10-13T11:35:45Zنقاط القوة: أقرب الأدوات لإعداد الإنتاج، وتصلح جهة إصدار للفريق أو للشبكة الداخلية، وليس لجهاز واحد فقط، وتدعم شهادات SSH كما ذكرنا في مقال الهوية والوصول المميز.
نقاط الضعف: سيرفر يحتاج إعداداً وكلمات مرور ونسخاً احتياطياً، وهذا أكثر مما يحتاجه مطور واحد على جهازه.
وماذا عن Traefik؟
أما Traefik الذي شرحناه في دليل Traefik لا يحمل جهة إصدار محلية، فإذا لم تعطه شهادة يستخدم شهادة يولدها بنفسه ولا يثق بها أي متصفح كما يذكر توثيق شهادات TLS، والحل أن تعطيه ملفات mkcert في قسم tls.certificates، أو أن تجعله يطلب الشهادات من step-ca عبر ACME.
الأسماء المحلية: من يجيب عن app.lab.test؟
رأينا في الجزء الأول أن *.localhost يعمل في المتصفح وcurl ولا يعمل في كل برنامج، والخيارات الأخرى خمسة:
- خدمات nip.io وsslip.io: نطاقات عامة تجيب عن أي اسم فيه عنوان IP بهذا العنوان نفسه، فالاسم
app.127.0.0.1.nip.ioيشير إلى127.0.0.1، والاسمapp.192-168-1-20.sslip.ioيشير إلى192.168.1.20، وقد تحققنا من ذلك بالأمرnslookup. وهي مفيدة لتجربة سريعة من الهاتف دون أي إعداد، لكنها تحتاج الإنترنت، وبعض الراوترات وسيرفرات DNS ترفض الإجابات التي تحمل عناوين خاصة لتمنع هجوم DNS Rebinding، كما يفعل الخيار--stop-dns-rebindفي dnsmasq، فلا تعتمد عليها في اختبارات آلية. ورخصة sslip.io هي Apache-2.0 وتستطيع تشغيل سيرفرها بنفسك. - سيرفر dnsmasq: سيرفر DNS صغير برخصة GPL، وسطر واحد مثل
address=/lab.test/127.0.0.1يجعل كل اسم تحتlab.testيشير إلى جهازك، وهو الخيار المعتاد على أجهزة Linux وmacOS، وقد استخدمناه في مقال DDNS ومختبرات الشبكات. - سيرفر CoreDNS: سيرفر DNS برخصة Apache-2.0 وهو المستخدم داخل Kubernetes، وفيه إضافة template التي تجيب عن كل اسم في نطاق بعنوان واحد، وهو مناسب داخل ملف Compose لأن ال Containers تستخدمه مباشرة، وهذا ما سوف نفعله في الجزء الثالث.
- نطاق حقيقي تملكه: سجل
*.dev.example-company.comفي DNS العام يشير إلى127.0.0.1يعمل في كل مكان، وتستطيع أن تصدر له شهادة حقيقية بالتحقق عبر DNS، لكنه ينشر أسماء بيئتك الداخلية للعالم، ويتأثر بنفس مشكلة DNS Rebinding.
ملف hosts: يربط اسماً كاملاً بعنوان، ولا يعرف النجمة، وقد اختبرنا ذلك في Container مؤقت دون شبكة بإضافة سطرين إلى /etc/hosts فيه:
docker run --rm --name lab-ldev-hosts --network none lab-ldev-pw:1 sh -c '
printf "127.0.0.1 *.lab.test\n127.0.0.1 app.lab.test\n" >> /etc/hosts; tail -2 /etc/hosts
for n in app.lab.test api.lab.test "*.lab.test"; do printf "%s -> " "$n"; getent hosts "$n" || echo "not found"; done'والمخرج سوف يكون كما يلي:
127.0.0.1 *.lab.test
127.0.0.1 app.lab.test
app.lab.test -> 127.0.0.1 app.lab.test
api.lab.test -> not found
*.lab.test -> 127.0.0.1 *.lab.testلاحظ أن api.lab.test لم يجده النظام، وأن السطر الأول لم يصبح قاعدة لكل الأسماء، وإنما أصبح اسماً غريباً حرفه الأول نجمة كما تصف صفحة hosts شكل السطر: عنوان ثم أسماء. وتعديل ملف hosts على جهازك يحتاج صلاحيات المدير، ويؤثر على كل البرامج، ولهذا لا نعدله في تجارب هذه السلسلة.
تخزين S3 ومحاكيات السحابة
لتخزين الملفات تشغل على جهازك سيرفراً يتحدث واجهة S3، وقد قارنا Garage وSeaweedFS وMinIO بالتفصيل في دليل S3 على سيرفرك ومقال أي تخزين S3 يناسبك الذي يشرح ما تغير في MinIO، وطريقة الاستخدام من الكود في مقال S3 من داخل تطبيقك، فلن نكرر ذلك هنا.
أما إذا كان تطبيقك يستخدم خدمات AWS أخرى مثل SQS وSNS وLambda وDynamoDB، فالأداة الأشهر كانت LocalStack، وقد تغير وضعها في 2026: فبحسب إعلان الشركة في 5 مارس 2026 أصبحت LocalStack for AWS تصدر منذ 23 مارس 2026 كـ Docker Image واحدة تحتاج رمز مصادقة Auth Token، أي تحتاج حساباً، وأصبح المستودع المفتوح على GitHub مؤرشفاً للقراءة فقط، والخطة المجانية Hobby في صفحة الأسعار مكتوب عليها أنها للهواة والاستخدام غير التجاري. وقد شغلنا localstack/localstack:latest دون رمز لنرى ذلك، والمخرج كما يلي:
LocalStack version: 2026.9.1
LocalStack build date: 2026-10-07
Localstack returning with exit code 55. Reason:
===============================================
License activation failed! 🔑❌
Reason: No credentials were found in the environment. Please set the LOCALSTACK_AUTH_TOKEN variable to a valid auth token.وهذا يعني أن فريقاً في شركة يحتاج خطة مدفوعة، أو أن يثبت إصداراً قديماً من قبل التغيير ويقبل أنه لن يحصل على إصلاحات، أو أن ينتقل إلى Moto برخصة Apache-2.0، وهي مكتبة Python تحاكي واجهات AWS ولها وضع سيرفر Server Mode تتصل به من أي لغة، وتغطيتها تختلف من خدمة إلى أخرى فراجعها قبل الاعتماد عليها. ورأينا أن الأفضل لأغلب الفرق أن تقلل الاعتماد على المحاكي أصلاً: S3 عبر سيرفر S3 حقيقي، والطوابير عبر طابور تشغله أنت في الاختبار، واختبار نهائي واحد على حساب AWS للاختبار.
محاكاة ال API الخارجي
عندما يعتمد تطبيقك على API خارجي، مثل بوابة دفع أو مزود رسائل SMS أو خدمة شحن، تحتاج سيرفراً يرد على طلباتك كما يرد ذلك ال API، ويعطيك الردود الصعبة التي لا يعطيها ال API الحقيقي بسهولة: الخطأ 500، والتأخير 30 ثانية، والرد المكرر. والأدوات الثلاث الأشهر:
- أداة WireMock برخصة Apache-2.0، مكتوب ب Java ويعمل كمكتبة داخل الاختبار أو كسيرفر في Container، ويسجل الطلبات والردود من ال API الحقيقي ثم يعيدها كما في توثيق التسجيل والإعادة، ويحاكي التأخير والأعطال. نقاط القوة: الأقدم والأغنى بالميزات ويوجد له مكتبات لعدة لغات. نقاط الضعف: الإعداد بملفات JSON يطول مع ال APIs الكبيرة.
- أداة Mockoon برخصة MIT، تطبيق سطح مكتب تصمم فيه الردود بواجهة رسومية، ثم تشغلها في CI بأداة Mockoon CLI. نقاط القوة: أسهل بداية لمن لا يحب ملفات الإعداد. نقاط الضعف: العمل المشترك للفرق وتشغيل المحاكاة كخدمة في نسخة Mockoon Pro المدفوعة، من 10 يورو للرخصة شهرياً بحد أدنى 5 رخص كما في صفحة الأسعار في 12 أكتوبر 2026.
- أداة Prism برخصة Apache-2.0، يقرأ ملف OpenAPI ويولد منه سيرفراً يرد بأمثلة الملف، ويعمل أيضاً كـ Proxy يتحقق من أن طلباتك والردود الحقيقية تطابق الملف. نقاط القوة: لا تكتب أي رد بيدك إذا كان لدى المزود ملف OpenAPI. نقاط الضعف: الردود ثابتة من الأمثلة ولا تحمل منطقاً.
وقبل أن تختار أداة تذكر أن المحاكي يرد بما تتوقعه أنت، وليس بما يفعله المزود فعلاً، ولهذا يأتي معه اختبار العقد Contract Test الذي شرحناه في مقال أنواع الاختبارات.
استقبال ال Webhooks على جهازك
ال Webhook هو الطلب الذي يرسله المزود إلى تطبيقك عندما يحدث شيء، مثل نجاح الدفع أو دفع كود إلى GitHub، والمشكلة أن المزود على الإنترنت وتطبيقك على localhost. والحل نفق Tunnel يخرج من جهازك إلى خدمة عامة تعطيك رابطاً، فيصل الطلب إلى الرابط ثم يعود عبر النفق إلى تطبيقك، كما تبين الصورة التالية:

وفكرة الأنفاق العكسية Reverse Tunnels شرحناها في مقال الأنفاق، وهنا ما يخص التطوير، والحدود كما قرأناها في 12 أكتوبر 2026:
- أنفاق Quick Tunnels من Cloudflare: الأمر
cloudflared tunnel --url http://localhost:3000يعطيك رابطاً علىtrycloudflare.comدون حساب، والبرنامج cloudflared برخصة Apache-2.0، وبحسب التوثيق يقبل النفق 200 طلب في الوقت نفسه ويرد بالخطأ 429 بعدها، ولا يدعم Server-Sent Events، والرابط يتغير مع كل تشغيل، وهو للتطوير والاختبار فقط. - خدمة ngrok: خدمة تجارية تحتاج حساباً، والخطة المجانية في صفحة الأسعار تعطي نطاق تطوير واحداً ثابتاً، و1GB من نقل البيانات، و20 ألف طلب HTTP، وثلاث نقاط اتصال Endpoints، وتعرض صفحة تحذير للزائر قبل طلبات المتصفح، وفيها أداة لفحص الطلبات وإعادة إرسالها مفيدة جداً مع ال Webhooks.
- خدمة smee.io: خدمة من مشروع Probot برخصة MIT، لا تفتح نفقاً، وإنما تستقبل ال Webhook على رابط قناة وترسله عبر Server-Sent Events إلى البرنامج
smee-clientعلى جهازك الذي يعيد إرساله إلى تطبيقك، ويقول المستودع صراحة إنها للتطوير وليست للإنتاج، ولاحظ أن من يعرف رابط القناة يرى ما يصل إليها.
وفي كل هذه الطرق تذكر أن الرابط العام يصل إلى تطبيقك وهو يعمل بإعداد التطوير، فتحقق من توقيع ال Webhook Signature في كودك كما في الإنتاج، وأغلق النفق عندما تنتهي، وإذا كان المزود يقدم أداة سطر أوامر ترسل أحداثاً تجريبية إلى localhost مباشرة فهي أفضل من النفق لأنها لا تنشر شيئاً.
مكتبة Testcontainers والاعتماديات داخل الاختبار نفسه
مكتبة Testcontainers مفتوحة المصدر برخصة MIT، لها نسخ ل Java وGo و.NET وNode.js وPython وRust وغيرها، وفكرتها أن الاختبار نفسه ينشئ Container لقاعدة البيانات أو للطابور أو لصندوق البريد قبل أن يبدأ، ويحذفه بعد أن ينتهي، وقد ذكرناها في قسم اختبار التكامل. والفرق بينها وبين ملف Compose في الغرض: ملف Compose يشغل بيئة تبقى تعمل وأنت تطور وتفتح التطبيق في المتصفح، وTestcontainers يشغل لكل مجموعة اختبارات نسخة نظيفة معزولة تختفي بعدها، فلا تتأثر الاختبارات ببيانات تركتها أنت أو تركها اختبار آخر. وأغلب الفرق تحتاج الاثنين: Compose للتطوير اليومي، وTestcontainers لاختبارات التكامل في CI.
مواصفات Dev Containers لبيئة عمل موحدة
كل ما سبق يشغل الخدمات حول تطبيقك، أما Dev Containers فيشغل بيئة التطوير نفسها، أي اللغة والأدوات والإضافات، داخل Container يصفه ملف devcontainer.json في المستودع، فيفتح كل مطور المشروع بالأدوات نفسها وبالإصدارات نفسها. والمواصفات مفتوحة برخصة CC-BY-4.0، وأداة سطر الأوامر برخصة MIT، وتدعمها بحسب قائمة الأدوات الداعمة بيئات مثل Visual Studio Code وVisual Studio وIntelliJ IDEA وخدمات مثل GitHub Codespaces وDevPod. وتستطيع أن تشير من devcontainer.json إلى ملف Compose، فتعمل بيئة العمل والخدمات المحلية معاً بأمر واحد، وإذا أردت بيئة تطوير في المتصفح لفريقك على سيرفرك فهذا موضوع مقال قادم بإذن الله في مسار منصات المطورين.
جدول القرار
| الحاجة | الخيار الأول | متى تختار غيره |
|---|---|---|
| التقاط البريد | Mailpit | أداة smtp4dev إذا احتجت IMAP أو POP3 لتفتح الرسائل من برنامج بريد، وتجنب MailHog في المشاريع الجديدة |
| شهادات HTTPS لمطور واحد | mkcert، أو tls internal إذا كان Caddy أمام التطبيق | لا تحتاج شيئاً إذا كان كل شيء على localhost في متصفح جهازك |
| شهادات HTTPS لفريق أو شبكة داخلية | جهة الإصدار step-ca عبر ACME | شهادة حقيقية بالتحقق عبر DNS إذا كان لديك نطاق |
| أسماء للمتصفح فقط | *.localhost | عندما تتصل الخدمات ببعضها بالأسماء |
| أسماء للمتصفح والخدمات معاً | CoreDNS أو dnsmasq لنطاق .test | خدمة nip.io لتجربة سريعة من الهاتف |
| تخزين S3 | Garage أو SeaweedFS أو MinIO بحسب المقارنة | حساب S3 للاختبار عند المزود قبل الإصدار |
| خدمات AWS الأخرى | مكتبة Moto، أو LocalStack بخطة تناسب استخدامك | حساب AWS للاختبار عندما تهمك الصلاحيات والحدود الحقيقية |
| واجهة API خارجية | WireMock | أداة Prism إذا كان لدى المزود ملف OpenAPI، وMockoon إذا كنت تفضل الواجهة الرسومية |
| استقبال Webhooks من مزود خارجي | أداة المزود نفسه إن وجدت، ثم Quick Tunnel من Cloudflare | خدمة ngrok إذا احتجت رابطاً ثابتاً وفحص الطلبات |
| الاعتماديات في الاختبار | Testcontainers | خدمات Compose في CI إذا كان المشروع صغيراً |
| توحيد بيئة الفريق | Dev Containers | ملف README بإصدارات ثابتة إذا كان الفريق شخصين |
أخطاء شائعة
- البحث عن رسالة قديمة في صندوق الالتقاط: Mailpit يحتفظ بآخر 500 وsmtp4dev بآخر 100، فاحذف الرسائل في بداية كل اختبار وابحث عن رسالة الاختبار نفسه.
- تفعيل Relay في Mailpit لكل العناوين: هذا يعيد مشكلة البريد الحقيقي من بيئة التطوير، فإن احتجته فقيده بعناوين تملكها.
- تشغيل
mkcert -installأو Caddy دونskip_install_trustدون أن تفكر: كل منهما يضيف جهة إصدار إلى مخزن جهازك، ومفتاحها على قرصك. - كتابة
*.lab.testفي ملف hosts: ملف hosts لا يعرف النجمة، كما رأينا في التجربة. - الاعتماد على
localstack/localstack:latestفي CI دون رمز: منذ مارس 2026 يتوقف ال Container بالرمز 55، ويفشل خط CI كله. - نفق مفتوح طوال اليوم: الرابط العام يصل إلى تطبيقك بإعداد التطوير، ومعه صفحات الأخطاء المفصلة.
الخلاصة
- أداة Mailpit هي الخيار الأول لالتقاط البريد بفضل ال API وميزة Chaos وحجمه، وsmtp4dev عندما تحتاج IMAP، وMailHog متوقف منذ 2020.
- في تجربتنا حفظ Mailpit آخر 500 رسالة من 600، وsmtp4dev آخر 100، وMailHog الستمائة كلها في الذاكرة.
- شهادة mkcert تعيش سنتين وثلاثة أشهر، وشهادة Caddy الداخلية 12 ساعة، وشهادة step-ca الافتراضية 24 ساعة، وجهة mkcert تستطيع أن توقع لأي اسم.
- ملف hosts لا يفهم النجمة، و
*.localhostللمتصفح، وCoreDNS أو dnsmasq لنطاق.testعندما تحتاج الأسماء في كل مكان. - محاكي LocalStack أصبح يحتاج رمز مصادقة منذ 23 مارس 2026، وMoto بديل مفتوح المصدر.
- أدوات WireMock وMockoon وPrism لمحاكاة ال API، وQuick Tunnel من Cloudflare أو ngrok أو smee.io لاستقبال ال Webhooks مع إغلاقها بعد الاستخدام.
- مكتبة Testcontainers للاعتماديات داخل الاختبار، وDev Containers لبيئة العمل نفسها، وملف Compose للبيئة التي تطور عليها كل يوم.
وفي الجزء الثالث بإذن الله نجمع Mailpit وmkcert وCaddy وCoreDNS مع تطبيق صغير في ملف Docker Compose واحد، ونكتب اختباراً آلياً يتأكد أن بريد استعادة كلمة المرور يصل، ثم نكسر البيئة بأربع طرق.
دليلبيئة التطوير المحلية: كيف تحاكي البريد وDNS وHTTPS على جهازكلماذا تستبدل البريد وDNS وHTTPS ببدائل محلية أثناء التطوير، وكيف يعامل المتصفح localhost، وأي النطاقات تتجنب.
دليلبيئة تطوير محلية كاملة بملف Docker Compose واحدنبني بيئة تطوير فيها البريد وHTTPS والأسماء المحلية، ونكتب اختباراً آلياً لبريد استعادة كلمة المرور ثم نكسرها.
دليلأي تخزين S3 مستضاف ذاتياً يناسبك؟ وما الذي تغير في MinIOمقارنة عملية بين أشهر أدوات تخزين S3 المستضافة ذاتياً بقياسات على جهاز واحد، لتعرف أيها يناسب سيرفرك وماذا حدث فعلاً ل MinIO.
دليلما هو النفق Tunnel في الشبكات؟ ومتى تحتاج إليهكيف تصل إلى قاعدة بيانات لا تراها من الإنترنت، وتنشر خدمة من خلف CGNAT دون فتح منافذ، ومتى تحتاج إلى VPN بدل النفق.سجل التحديثات
- أكتوبر 2026: كتابة المقال، والقياسات على Mailpit v1.31.5 وsmtp4dev 3.15.0 وMailHog v1.0.1 مع Python 3.13 على Docker Engine 29.8.0، والشهادات من mkcert v1.4.4 وCaddy 2.11.7 وstep-ca 0.30.2، وتشغيل localstack/localstack:latest (الإصدار 2026.9.1)، والرخص وحالة المشاريع من GitHub، وحدود ngrok وCloudflare Quick Tunnels وخطط LocalStack كما قرأناها في 12 أكتوبر 2026.