Docker环境下Django LiveServer无法连接SMTP服务器的解决方法
问题
我在Docker中运行Django 4.1应用,测试套件需使用同样部署在Docker容器中的开发SMTP服务器(配置见下方docker-compose.yml)。我用Selenium驱动针对Django LiveServer实例执行测试,同时采用1secmail作为测试用临时邮箱。
SMTP服务器仅在Django非LiveServer运行模式下能正常配合(即Django可通过其发送邮件),但当我用Selenium驱动测试涉及SMTP的场景时,邮件始终无法发送。请问如何让测试环境中的Django与SMTP服务器实现通信?
我的docker-compose.yml
version: '3.8' services: myapp: build: . command: python manage.py runserver 0.0.0.0:8000 volumes: - ./:/usr/src/app/ ports: - 8009:8000 env_file: - ./.env.dev links: - selenium-chrome - dev-smtp-server myapp-db: image: postgres:14-alpine volumes: - postgres_data:/var/lib/postgresql/data/ environment: - POSTGRES_USER=blabla - POSTGRES_PASSWORD=blabla - POSTGRES_DB=blabla selenium-chrome: image: selenium/standalone-chrome ports: - 4444:4444 # actual Selenium - 5900:5900 # VNC server dev-smtp-server: image: bytemark/smtp restart: always volumes: postgres_data:
我的测试夹具
... from pytest_django.live_server_helper import LiveServer @pytest.fixture(scope="session") def test_server() -> LiveServer: host = socket.gethostbyname(socket.gethostname()) if host not in settings.ALLOWED_HOSTS: settings.ALLOWED_HOSTS.append(host) server = LiveServer(host) yield server server.stop() @pytest.fixture def driver(): options = webdriver.ChromeOptions() driver = webdriver.Remote( command_executor="http://selenium-chrome:4444/wd/hub", # connect to the Selenium container defined in docker-compose options=options, ) yield driver driver.quit() @pytest.fixture def forgot_my_password_page_driver(driver: Remote, test_server: LiveServer): forgotMyPasswordURL = reverse("password_reset") driver.get(f"{test_server.url}{forgotMyPasswordURL}") return driver @pytest.fixture def email_address_for_which_forgotten_password_form_has_just_been_filed( forgot_my_password_page_driver, db ): emailInput = forgot_my_password_page_driver.find_element(By.NAME, "email") emailAddress = OneSecMailAPI.get_random_email_address() emailInput.send_keys(emailAddress) sendButton = forgot_my_password_page_driver.find_element( By.CSS_SELECTOR, "input[type='submit']" ) sendButton.click() return emailAddress
失败的测试
def test_forgot_my_password_form_sends_an_email_to_actually_reset_your_password( email_address_for_which_forgotten_password_form_has_just_been_filed, ): emailAddress = email_address_for_which_forgotten_password_form_has_just_been_filed emailDomain = emailAddress.split("@")[1] emailReceived = False loopCounter = 0 while not emailReceived and loopCounter < 5: messages = OneSecMailAPI.get_messages(login=emailAddress, domain=emailDomain) if len(messages) == 0: time.sleep(1) loopCounter = loopCounter + 1 else: emailReceived = True assert emailReceived
解决方案
问题根源
pytest_django的LiveServer默认会启动独立的测试进程,同时自动将Django的EMAIL_BACKEND替换为locmem.EmailBackend(内存后端),直接跳过真实SMTP发送流程;另外测试进程可能未正确加载主应用的SMTP连接配置,导致无法访问容器内的SMTP服务。
修复步骤
1. 强制LiveServer使用真实SMTP后端
在test_server夹具中手动覆盖测试环境的邮件配置,确保指向容器内的SMTP服务:
@pytest.fixture(scope="session") def test_server() -> LiveServer: host = socket.gethostbyname(socket.gethostname()) if host not in settings.ALLOWED_HOSTS: settings.ALLOWED_HOSTS.append(host) # 强制使用真实SMTP后端,禁用内存后端 settings.EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 指向docker-compose中SMTP服务的名称 settings.EMAIL_HOST = 'dev-smtp-server' # bytemark/smtp默认端口为25 settings.EMAIL_PORT = 25 # 根据需要设置其他SMTP参数,比如无需认证 settings.EMAIL_USE_TLS = False settings.EMAIL_HOST_USER = '' settings.EMAIL_HOST_PASSWORD = '' server = LiveServer(host) yield server server.stop()
2. 确保测试进程能访问SMTP容器
docker-compose中myapp服务已添加links到dev-smtp-server,只要测试进程使用服务名dev-smtp-server作为SMTP主机,就能正常建立连接。
3. 优化测试等待逻辑
适当延长等待时间,避免因邮件发送延迟导致测试失败:
def test_forgot_my_password_form_sends_an_email_to_actually_reset_your_password( email_address_for_which_forgotten_password_form_has_just_been_filed, ): emailAddress = email_address_for_which_forgotten_password_form_has_just_been_filed emailLogin, emailDomain = emailAddress.split("@") emailReceived = False loopCounter = 0 # 延长总等待时间至10秒,每次间隔2秒 while not emailReceived and loopCounter < 5: messages = OneSecMailAPI.get_messages(login=emailLogin, domain=emailDomain) if len(messages) > 0: emailReceived = True break time.sleep(2) loopCounter += 1 assert emailReceived, f"未收到重置密码邮件,目标邮箱:{emailAddress}"
4. 验证配置生效(可选)
在夹具中添加日志输出,确认邮件配置是否正确加载:
import logging @pytest.fixture(scope="session") def test_server() -> LiveServer: # ... 原有代码 ... logging.info(f"当前EMAIL_BACKEND: {settings.EMAIL_BACKEND}") logging.info(f"当前EMAIL_HOST: {settings.EMAIL_HOST}") # ... 原有代码 ...
内容的提问来源于stack exchange,提问作者Buddyshot
相关产品推荐
相关产品推荐

