flask-mail icon indicating copy to clipboard operation
flask-mail copied to clipboard

Unit testing a Flask route that sends mail

Open ATenderholt opened this issue 7 years ago • 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.

ATenderholt avatar Feb 14 '18 14:02 ATenderholt

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: https://github.com/vimalloc/flask-jwt-extended/issues/176

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

DenisKuplyakov avatar Jul 22 '18 14:07 DenisKuplyakov

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': '[email protected]',
        }
        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.

grafolean avatar Jun 20 '20 19:06 grafolean