I want to test the following function in pytest without actually creating an SMTP server and sending email to the specified address.
def send_email_investor_owner_occupier(self,user_type,smtp_server):
message=MIMEMultipart()
message['From']=self.email_address
message['To']=self.email_address
suburb_set={suburb for location in user_type.locations for suburb in location.keys()}
suburb_string=','.join(suburb_set)
message['Subject']=f'Investment Properties in {suburb_string} last updated {user_type.date_posted}'
body= f'{user_type.properties} with {user_type.bedrooms} bedrooms, {user_type.bathrooms} bathrooms,{user_type.car_spaces} car spaces, priced between ${user_type.min_price} and ${user_type.max_price} in {suburb_string} last updated {user_type.date_posted}. Key metrics calculated for {user_type.loan_type} {user_type.variable_loan_type if user_type.variable_loan_type!=None else ""} loan with {user_type.lvr/100} lvr for {user_type.loan_term} years with {user_type.mortgage_interest}% interest.'
message.attach(MIMEText(body,"plain"))
message.attach('property_data.csv',self.property_data_io.getvalue(),'text/csv')
with smtplib.SMTP(smtp_server,self.port) as server:
server.starttls()
server.login(self.email_address,self.password)
server.sendmail(self.email_address,self.email_address,message.as_string())
server.quit()
I am aware that SMTP server can be mocked using the python unittest module as per https://jingwen-z.github.io/how-to-send-emails-with-python/ but I cannot figure it out using pytest. I would like to be able to do this in pytest if possible given all my other tests use the module.
CodePudding user response:
I'm not entirely sure what you are looking for, as your function doesn't return anything. But i guess you can assert which functions are called inside send_email_investor_owner_occupier
.
To use mocker in pytest, what i do is install the pytest-mock module. You can then define you test as:
def test_something(mocker):
mocker.patch(foo.bar, return_value=True)
assert foo.bar()
CodePudding user response:
If you know how to mock in unittest, then mocking in pytest is pretty easy.
Say that your python module that contains the send_email_investor_owner_occupier
method is called my_email_sender.py
, then you patch my_email_sender.smtplib.SMTP
.
One way of doing it would be using the patch decorator:
from unittest.mock import patch
@patch("my_email_sender.smtplib.SMTP")
def test_send_email_investor_owner_occupier_patch_decorator(smtp_mock):
# call send_email_investor_owner_occupier
# assert like: smtp_mock.assert_called_once_with("server", port)
pass
Another way would be to use the pytest-mock plugin to pytest:
pip install pytest-mock
def test_send_email_investor_owner_occupier_pytest_mock_plugin(mocker):
smtp_mock = mocker.MagicMock(name='smtp_mock')
mocker.patch('my_email_sender.smtplib.SMTP', new=smtp_mock)
# call send_email_investor_owner_occupier
# assert like: smtp_mock.assert_called_once_with("server", port)
pass
This might seem a bit tedious, but if you have several tests which check the mail sending and you want to mock all of them easily without copying the same code you can mix this approach with a pytest fixture:
import pytest
@pytest.fixture
def smtp_mock(mocker):
smtp_mock = mocker.MagicMock(name='smtp_mock')
mocker.patch('my_email_sender.smtplib.SMTP', new=smtp_mock)
yield smtp_mock
def test_send_email_investor_owner_occupier_pytest_fixture(smtp_mock):
# call send_email_investor_owner_occupier
# assert like: smtp_mock.assert_called_once_with("server", port)
pass
def test_send_email_investor_owner_occupier_pytest_fixture_2(smtp_mock):
# call send_email_investor_owner_occupier
# assert like: smtp_mock.assert_called_once_with("server", port)
pass
def test_send_email_investor_owner_occupier_pytest_fixture_3(smtp_mock):
# call send_email_investor_owner_occupier
# assert like: smtp_mock.assert_called_once_with("server", port)
pass