Paulo Poiati | Blog

Testing flash messages in Flask

Testing with Flask is generally straightforward but some things aren’t trivial. The Flask-Testing extension helps us with some really useful assertions like TestCase#assert_context and TestCase#assert_template_used but it miss a way to test flash messages.

One of the ways to test flashes is asserting if the message is in the response data.

Suppose the production code.

@app.route('/post/')
def new_post(self):
  if storage.create_post(request.form['post']):
    flash(u'Post added')
    return redirect(url_for('home'))
  return render_template('new-post.html')

And the test code.

def should_flash_a_success_message(self):
  response = self.client.post('/post/', data=self.valid_post_data,
                              follow_redirects=True)

  assert 'Post added' in response.data

This works but personally I don’t like it. One reason is we need to follow the redirect and this can lead to testing the same thing twice because we should have a test for the home route already.

The other reason is related to isolation. If we want to test the view without rendering the template we can’t because we are making assertions in the generated document.

Curiosity: Rails by default doesn’t render any view in controller specs. In Flask-Testing it renders but you can turn off template rendering.

Another way to test flash messages is using a mock. You replace the flash method with a mock and use expectations to assert flash calls. This works but I like to avoid mocks when possible.

My solution consist in defining a new method to do the job. I recommend you to define it in a base test class so you can reuse it.

from flask.ext.testing import TestCase

import myapp


class BaseTest(TestCase):
                                                                                
    def create_app(self):
        return myapp.create_app(TESTING=True)
                                                                                                 
    def assert_flashes(self, expected_message, expected_category='message'):
        with self.client.session_transaction() as session:
            try:
                category, message = session['_flashes'][0]
            except KeyError:
                raise AssertionError('nothing flashed')
            assert expected_message in message
            assert expected_category == category

And now my test looks like this.

def should_flash_a_success_message(self):
  self.client.post('/post/', data=self.valid_post_data)

  self.assert_flashes('Post added')

Don’t forget to subclass BaseTest in your test classes. Another trick thing about flashes is that they need the session to work and sessions doesn’t work without the SECRET_KEY set in Flask so be sure you set it before running the tests.

comments powered by Disqus