Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Flask routes do and don't exist

I'm creating a large number of Flask routes using regular expressions. I'd like to have a unit test that checks that the correct routes exist and that incorrect routes 404.

One way of doing this would be to spin up a local server and use urllib2.urlopen or the like. However, I'd like to be able to run this test on Travis, and I'm assuming that's not an option.

Is there another way for me to test routes on my application?

like image 687
Tim Hopper Avatar asked Jul 29 '15 20:07

Tim Hopper


People also ask

How do you get all the routes on a flask?

To get list of all routes defined in the Python Flask app, we can use the app. url_map property. to print the list of routes in our app with print(app. url_map) .

What does flask route do?

App routing is used to map the specific URL with the associated function that is intended to perform some task. It is used to access some particular page like Flask Tutorial in the web application.


3 Answers

Use the Flask.test_client() object in your unittests. The method returns a FlaskClient instance (a werkzeug.test.TestClient subclass), making it trivial to test routes.

The result of a call to the TestClient is a Response object, to see if it as 200 or 404 response test the Response.status_code attribute:

with app.test_client() as c:
    response = c.get('/some/path/that/exists')
    self.assertEquals(response.status_code, 200)

or

with app.test_client() as c:
    response = c.get('/some/path/that/doesnt/exist')
    self.assertEquals(response.status_code, 404)

See the Testing Flask Applications chapter of the Flask documentation.

like image 181
Martijn Pieters Avatar answered Oct 18 '22 03:10

Martijn Pieters


Martjin's answer surely solve your issue, but some times you don't have the time (or will) to mock all the components you call in a route you want to test for existence.

And why would you need to mock? Well, the call get('some_route') will first check for this route to exists and then ... it will be executed!

If the view is a complex one and you need to have fixtures, envs variables and any other preparation just for test if the route exists, then you need to think again about your test design.

How to avoid this:

You can list all the routes in your app. An check the one you're testing is in the list.

In the following example, you can see this in practice with the implementation of a site-map.

from flask import Flask, url_for

app = Flask(__name__)

def has_no_empty_params(rule):
    defaults = rule.defaults if rule.defaults is not None else ()
    arguments = rule.arguments if rule.arguments is not None else ()
    return len(defaults) >= len(arguments)


@app.route("/site-map")
def site_map():
    links = []
    for rule in app.url_map.iter_rules():
        # Filter out rules we can't navigate to in a browser
        # and rules that require parameters
        if "GET" in rule.methods and has_no_empty_params(rule):
            url = url_for(rule.endpoint, **(rule.defaults or {}))
            links.append((url, rule.endpoint))
    # links is now a list of url, endpoint tuples

references:

get a list of all routes defined in the app

Helper to list routes (like Rail's rake routes)

like image 23
Raydel Miranda Avatar answered Oct 18 '22 01:10

Raydel Miranda


Another way of testing a URL without executing the attached view function is using the method match of MapAdapter.

from flask import Flask

app = Flask(__name__)

@app.route('/users')
def list_users():
    return ''

@app.route('/users/<int:id>')
def get_user(id):
    return ''

Testing

# Get a new MapAdapter instance. For testing purpose, an empty string is fine
# for the server name.
adapter = app.url_map.bind('')

# This raise werkzeug.exceptions.NotFound.
adapter.match('/unknown')

# This raises werkzeug.exceptions.MethodNotAllowed,
# Although the route exists, the POST method was not defined.
adapter.match('/users', method='POST')

# No exception occurs when there is a match..
adapter.match('/users')
adapter.match('/users/1')

From Werkzeug documentation:

you get a tuple in the form (endpoint, arguments) if there is a match.

Which may be useful in certain testing scenarios.

like image 41
José María Avatar answered Oct 18 '22 02:10

José María