pallets-eco/flask-mail

Unit testing a Flask route that sends mail

Closed this issue · 2 comments

I'm trying to verify that an email is sent when a specific route is unit tested. Here's the relevant part of my (failing) test:

with mailer.mail.record_messages() as outbox:
    response = self.client.post('/route', data=data)

    assert len(outbox) == 1

I can run the app manually and verify that an email is actually sent when visiting /route.

Any ideas? The example in the documentation only shows testing mail being sent directly from the context, not by posting to a route.

I have a similarly looking problem with testing endpoint that sends email. Usage of record_messages requires to have application context active, but flask's documentation says to test requests outside of application context, creating it only for database operations, etc.

Moreover here in flask docs we read that:

When the request ends it pops the request context then the application context. Typically, an application context will have the same lifetime as a request.

And some extensions rely on this: vimalloc/flask-jwt-extended#176

Can you please provide the example of how to test sending of email inside request processing?

I know this issue is more than 2 years old, but I spent way too much time searching for a suitable solution, so I'm posting a simplified example here in case it helps someone.

In your endpoint (where you wish to send the mail) you do:

@api.route('/signup', methods=['POST'])
def signup_post():
    msg = Message("Welcome!", ...)  # fill in other params as needed
    mail = Mail(flask.current_app)
    with mail.record_messages() as outbox:
        mail.send(msg)
        # this will save the list of messages to "global" object in app context
        # so that tests can inspect them:
        flask.g.outbox = outbox
    return "", 200

Then in test:

def test_signup_successful(app_client):
    with app_client:  # IMPORTANT: this will keep app context alive so that we can inspect outbox
        data = {
            'email': 'test@example.com',
        }
        r = app_client.post('/api/persons/signup/new', data=json.dumps(data), content_type='application/json')
        assert r.status_code == 200
        # now grab the list of sent mail messages from flask.g:
        assert len(flask.g.outbox) == 1
        assert flask.g.outbox[0].subject == "Welcome!"

Note that this example doesn't show config and similar, there are other resources for that... this example shows only how to access the request's app context from a pytest test. If there's a nicer way to achieve that, please do share.