Package: pytest-smtpd
pytest-smtpd⚠Not intended for use with production systems.⚠This fixture is intended to address cases where to test an application that sends an email, it needs to be intercepted for subsequent processing. For example, sending an email with a code for password reset or two-factor authentication. This fixture allows a test to trigger the email being sent, ensure that it's sent, and read the email.InstallingTo install using pip, first upgrade pip to the latest version to avoid any issues installingcryptography:python-mpipinstall--upgradepip
pipinstallpytest-smtpdOr, if you're using setuptools, it can be included in theextras_requireargument of asetup.pyfile:setup(...extras_require={"test":["pytest","pytest-smtpd",],},)and then installed with pip (-e assumes that you want your project to be editable):python-mpipinstall--upgradepip
pipinstall-e.[test]UsingTheSMTPDFixplugin,smtpd, automatically registers for use with pytest when you install smtpdfix. To use it simply add to your test method.fromsmtplibimportSMTPdeftest_sendmail(smtpd):from_addr="from.addr@example.org"to_addrs="to.addr@example.org"msg=(f"From:{from_addr}\r\n"f"To:{to_addrs}\r\n"f"Subject: Foo\r\n\r\n"f"Foo bar")withSMTP(smtpd.hostname,smtpd.port)asclient:client.sendmail(from_addr,to_addrs,msg)assertlen(smtpd.messages)==1To use STARTTLS:fromsmtplibimportSMTPdeftest_sendmail(smtpd):smptd.config.use_starttls=Truefrom_="from.addr@example.org"to_="to.addr@example.org"msg=(f"From:{from_}\r\n"f"To:{to_}\r\n"f"Subject: Foo\r\n\r\n"f"Foo bar")withSMTP(smtpd.hostname,smtpd.port)asclient:client.starttls()# Note that you need to call starttls first.client.sendmail(from_addr,to_addrs,msg)assertlen(smtpd.messages)==1To use TLS encryption on the connections:fromsmtplibimportSTMP_SSL# Note the different client class.deftest_custom_certificate(smtpd):smtpd.config.use_ssl=Truefrom_="from.addr@example.org"to_="to.addr@example.org"msg=(f"From:{from_}\r\n"f"To:{to_}\r\n"f"Subject: Foo\r\n\r\n"f"Foo bar")withSMTP_SSL(smtpd.hostname,smtpd.port)asclient:client.sendmail(from_addr,to_addrs,msg)assertlen(smtpd.messages)==1The certificates included with the fixture will work for addresses localhost, localhost.localdomain, 127.0.0.1, 0.0.0.1, ::1. If using other addresses the key (key.pem) and certificate (cert.pem) must be in a location specified underSMTP_SSL_CERTS_PATH.ConfigurationConfiguration is handled through properties in theconfigof the fixture and are initially set from environment variables:PropertyVariableDefaultDescriptionhostSMTPD_HOST127.0.0.1or::1The hostname that the fixture will listen on.portSMTPD_PORTa random free portThe port that the fixture will listen on.ready_timeoutSMTPD_READY_TIMEOUT10.0The seconds the server will wait to start before raising aTimeoutError.login_usernameSMTPD_LOGIN_NAMEuserUsername for default authentication.login_passwordSMTPD_LOGIN_PASSWORDpasswordPassword for default authentication.use_sslSMTPD_USE_SSLFalseWhether the fixture should use fixed TLS/SSL for transactions. If using smtplib requires thatSMTP_SSLbe used instead ofSMTP.use_starttlsSMTPD_USE_STARTTLSFalseWhether the fixture should use StartTLS to encrypt the connections. If usingsmtplibrequires thatSMTP.starttls()is called before other commands are issued. Overridesuse_tlsas the preferred method for securing communications with the client.enforce_authSMTPD_ENFORCE_AUTHFalseIf set to true then the fixture refuses MAIL, RCPT, DATA commands until authentication is completed.ssl_cert_pathSMTPD_SSL_CERTS_PATH./certs/The path to the key and certificate in PEM format for encryption with SSL/TLS or StartTLS.ssl_cert_filesSMTPD_SSL_CERT_FILEandSMTPD_SSL_KEY_FILE("cert.pem", None)A tuple of the path for the certificate file and key file in PEM format.AlternativesMany libraries for sending email have built-in methods for testing and using these methods should generally be prefered over pytest-smtpd. Some known solutions:fastapi-mail: has arecord_messsages()method to intercept the mail. Instructions on how to suppress the sending of mail and implement it can be seen athttps://sabuhish.github.io/fastapi-mail/example/#unittests-using-fastapimailflask-mail: has a method to suppress sending and capture the email for testing purposes.InstructionsDevelopingTo develop and test smtpdfix you will need to installpytest-asyncioto run asynchronous tests,isortto sort imports andflake8to lint. To install in a virtual environment for development:python-mvenvvenv
./venv/scripts/activate
pipinstall-e.[dev]Code is tested using tox:toxQuick tests can be handled by running pytest directly:pytestWe include apre-commitconfiguration file to automate checks and clean up imports before pushing code. In order to install pre-commit git hooks:pipinstallpre-commit
pre-commitinstallKnown IssuesFirewalls may interfere with the operation of the smtp server.Authenticating with LOGIN and PLAIN mechanisms fails over TLS/SSL, but works with STARTTLS.smtpdfix Issue #10Currently no support for termination through signals.smtpdfix Issue #4Written with ☕ and ❤ in Montreal, QC