3

I have the following test in my app:

@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)

According to the documentation if the app.config['TESTING'] is True, emails will be suppressed. The test suite passes, but the email message is actually sent.

@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)

I've also tried setting the app's config's MAIL_SUPPRESS_SEND to True (as well as False, which fails the assert).

What am I doing wrong?

Chuck
  • 4,662
  • 2
  • 33
  • 55

1 Answers1

0

Turns out the answer was in how I was creating the Flask app. The pytest fixture created the app, which had the default (i.e., production) configuration, which means it wasn't in test mode when the mail object was created, and when I updated the configuration after app creation, the mail object didn't know about it. The solution was to pass the create_app the config from the pytest fixture.

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')
    ...
Chuck
  • 4,662
  • 2
  • 33
  • 55