为什么 Flask-Mail 在测试期间实际上发送消息
Why is Flask-Mail actually sending messages during testing
我的应用程序中有以下测试:
@pytest.mark.usefixtures('database')
def test_reset_email(client):
assert current_app.config['TESTING']
with mail.record_messages() as outbox:
response = client.post('/reset', data=dict(email=tconst.ADMIN_EMAIL),
follow_redirects=True)
msg = outbox[-1]
assert const.RESET_PASSWORD_REQUEST_FLASH in str(response.data)
assert msg.subject == const.RESET_EMAIL_SUBJECT
assert 'Reset Password' in msg.html
assert 'Reset Password' in msg.body
pattern = ('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*'
+ '\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+') # noqa W605
url = re.findall(pattern, msg.body)[0]
path = urlparse(url).path
response = client.post(path, data=dict(password='newpass'), follow_redirects=True)
log_in(client, tconst.ADMIN_EMAIL, 'newpass')
assert 'Logout' in str(response.data)
根据文档,如果 app.config['TESTING']
是 True
,电子邮件将被禁止。测试套件通过,但电子邮件消息实际已发送。
@user_blueprint.route('/reset', methods=['GET', 'POST'])
def reset():
"""Sends a tokenized email link to the user. Fails silently if email doesn't exist."""
form = ResetPasswordForm()
if form.validate_on_submit():
email = form.email.data
user = User.select_by_email(email=email).first()
if user:
timed_serializer = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
token = timed_serializer.dumps(email, salt='recovery-token')
url = url_for('user.reset_with_token', token=token, _external=True)
body = render_template('email/recover.txt', url=url)
html = render_template('email/recover.html', url=url)
msg = Message(body=body, html=html, recipients=[email],
subject=const.RESET_EMAIL_SUBJECT)
mail.send(msg)
flash(const.RESET_PASSWORD_REQUEST_FLASH, 'success')
return redirect(url_for('user.login'))
return render_template('user/reset.html', form=form)
我还尝试将应用的配置 MAIL_SUPPRESS_SEND
设置为 True
(以及 False
,但 assert
失败)。
我做错了什么?
原来答案在于我创建 Flask 应用程序的方式。 pytest
fixture 创建了具有默认(即生产)配置的应用程序,这意味着在创建 mail
对象时以及我在应用程序后更新配置时它不处于测试模式创建时,mail
对象不知道它。解决方案是从 pytest
夹具传递 create_app
配置。
def create_app(config=None):
...
config = config or os.getenv('APP_SETTINGS', 'app.ProdConfig')
app.config.from_object(config)
...
@pytest.fixture(scope='module')
def app():
app = create_app(config='app.TestConfig')
...
我的应用程序中有以下测试:
@pytest.mark.usefixtures('database')
def test_reset_email(client):
assert current_app.config['TESTING']
with mail.record_messages() as outbox:
response = client.post('/reset', data=dict(email=tconst.ADMIN_EMAIL),
follow_redirects=True)
msg = outbox[-1]
assert const.RESET_PASSWORD_REQUEST_FLASH in str(response.data)
assert msg.subject == const.RESET_EMAIL_SUBJECT
assert 'Reset Password' in msg.html
assert 'Reset Password' in msg.body
pattern = ('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*'
+ '\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+') # noqa W605
url = re.findall(pattern, msg.body)[0]
path = urlparse(url).path
response = client.post(path, data=dict(password='newpass'), follow_redirects=True)
log_in(client, tconst.ADMIN_EMAIL, 'newpass')
assert 'Logout' in str(response.data)
根据文档,如果 app.config['TESTING']
是 True
,电子邮件将被禁止。测试套件通过,但电子邮件消息实际已发送。
@user_blueprint.route('/reset', methods=['GET', 'POST'])
def reset():
"""Sends a tokenized email link to the user. Fails silently if email doesn't exist."""
form = ResetPasswordForm()
if form.validate_on_submit():
email = form.email.data
user = User.select_by_email(email=email).first()
if user:
timed_serializer = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
token = timed_serializer.dumps(email, salt='recovery-token')
url = url_for('user.reset_with_token', token=token, _external=True)
body = render_template('email/recover.txt', url=url)
html = render_template('email/recover.html', url=url)
msg = Message(body=body, html=html, recipients=[email],
subject=const.RESET_EMAIL_SUBJECT)
mail.send(msg)
flash(const.RESET_PASSWORD_REQUEST_FLASH, 'success')
return redirect(url_for('user.login'))
return render_template('user/reset.html', form=form)
我还尝试将应用的配置 MAIL_SUPPRESS_SEND
设置为 True
(以及 False
,但 assert
失败)。
我做错了什么?
原来答案在于我创建 Flask 应用程序的方式。 pytest
fixture 创建了具有默认(即生产)配置的应用程序,这意味着在创建 mail
对象时以及我在应用程序后更新配置时它不处于测试模式创建时,mail
对象不知道它。解决方案是从 pytest
夹具传递 create_app
配置。
def create_app(config=None):
...
config = config or os.getenv('APP_SETTINGS', 'app.ProdConfig')
app.config.from_object(config)
...
@pytest.fixture(scope='module')
def app():
app = create_app(config='app.TestConfig')
...