0

I am running a Django (4.1) app in Docker. As part of our test suite, I would like to make use of a development SMTP server which is also running in a Docker container (see docker-compose.yml below). I am using a Selenium driver to run the tests against a Django LiveServer instance. I am also using 1secmail as a temporary mailbox for the tests.

The SMTP server is working nicely with Django (i.e. Django is able to send emails with it) only if Django is not running from a LiveServer. Whenever I try to programmatically test a scenario where SMTP is involved (with a Selenium driver), the email never gets sent (see the failing test below).

The question is simple: how do I make Django and the SMTP server talk to each other in the tests environment?

My 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:

My test fixtures

...
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

The failing test

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
Buddyshot
  • 1,614
  • 1
  • 17
  • 44
  • What error do you get when you run that test? Where do you configure the location of the SMTP relay? – David Maze Dec 10 '22 at 17:43
  • The test fails because `emailReceived` is asserted to False when we expect it to be True.The SMTP host and port are left with the default values from the docker image (localhost and port 25) and we tell Django to use it by setting the `EMAIL_HOST` and `EMAIL_PORT` in `settings.py`. As mentioned, this works well when interacting with Django from a browser as a human with the main server (on port 8000 in the container and exposed as 8009 on my machine) – Buddyshot Dec 11 '22 at 01:51
  • So just to be clear, the same test run manually (as a human clicking and typing) on the main server actually succeeds. The email does get sent in this configuration. – Buddyshot Dec 11 '22 at 01:57
  • After some more testing, I think I found the source of the problem. The email backend used by the test servers is overridden by Django to a `locmem` instead of `smtp`. [This](https://stackoverflow.com/a/16236241/3281427) helped me too. – Buddyshot Dec 11 '22 at 14:58

0 Answers0