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