broox
broox

Reputation: 3960

How can I test a custom Flask error page?

I'm attempting to test a custom error page in flask (404 in this case).

I've defined my custom 404 page as such:

@app.errorhandler(404)
def page_not_found(e):
    print "Custom 404!"
    return render_template('404.html'), 404

This works perfectly when hitting an unknown page in a browser (I see Custom 404! in stdout and my custom content is visible). However, when trying to trigger a 404 via unittest with nose, the standard/server 404 page renders. I get no log message or the custom content I am trying to test for.

My test case is defined like so:

class MyTestCase(TestCase):
    def setUp(self):
        self.app = create_app()
        self.app_context = self.app.app_context()
        self.app.config.from_object('config.TestConfiguration')
        self.app.debug = False # being explicit to debug what's going on...
        self.app_context.push()
        self.client = self.app.test_client()

    def tearDown(self):
        self.app_context.pop()

    def test_custom_404(self):
        path = '/non_existent_endpoint'
        response = self.client.get(path)
        self.assertEqual(response.status_code, 404)
        self.assertIn(path, response.data)

I have app.debug explicitly set to False on my test app. Is there something else I have to explicitly set?

Upvotes: 9

Views: 5174

Answers (3)

Ange Uwase
Ange Uwase

Reputation: 581

In reference to the following comment you made "If I simply move that error handler up into the create_app() method, everything works fine... but it feels a bit gross? Maybe?": You can define a function to register an error handler and call that in your create_app function:

def create_app():
    app = Flask(__name__)
    app.config.from_object('config.BaseConfiguration')
    app.secret_key = app.config.get('SECRET_KEY')
    app.register_blueprint(main.bp)
    register_error_pages(app)
    return app

app = create_app()

# Custom error pages

def register_error_pages(app):
    @app.errorhandler(404)
    def page_not_found(e):
        return render_template('404.html'), 404

That way if you have more custom error handlers you want to register (403, 405, 500) you can define them inside the register_error_pages function instead of your create_app function.

Upvotes: 0

user3874521
user3874521

Reputation: 11

The Flask application object has an error_handler_spec property that can be mocked to solve this:

A dictionary of all registered error handlers. The key is None for error handlers active on the application, otherwise the key is the name of the blueprint. Each key points to another dictionary where the key is the status code of the http exception. The special key None points to a list of tuples where the first item is the class for the instance check and the second the error handler function.

So something like this in your test method should work:

mock_page_not_found = mock.magicMock()  
mock_page_not_found.return_value = {}, 404

with mock.patch.dict(self.app.error_handler_spec[None], {404: mock_page_not_found}):
  path = '/non_existent_endpoint'
  response = self.client.get(path)

  self.assertEqual(response.status_code, 404)
  mock_page_not_found.assert_called_once()

Upvotes: 1

broox
broox

Reputation: 3960

After revisiting this with fresh eyes, it's obvious that the problem is in my initialization of the application and not in my test/configuration. My app's __init__.py basically looks like this:

def create_app():
    app = Flask(__name__)
    app.config.from_object('config.BaseConfiguration')
    app.secret_key = app.config.get('SECRET_KEY')
    app.register_blueprint(main.bp)
    return app

app = create_app()

# Custom error pages

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

Notice that the error handler is attached to @app outside of create_app(), the method I'm calling in my TestCase.setUp() method.

If I simply move that error handler up into the create_app() method, everything works fine... but it feels a bit gross? Maybe?

def create_app():
    app = Flask(__name__)
    app.config.from_object('config.BaseConfiguration')
    app.secret_key = app.config.get('SECRET_KEY')
    app.register_blueprint(main.bp)

    # Custom error pages
    @app.errorhandler(404)
    def page_not_found(e):
        return render_template('404.html'), 404

    return app

This ultimately answers my question and fixes my problem, but I'd love other thoughts on how to differently register those errorhandlers.

Upvotes: 9

Related Questions