You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 13:50:33