16

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?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Tim Hopper
  • 945
  • 2
  • 13
  • 29
  • Did you read the [testing chapter](http://flask.pocoo.org/docs/0.10/testing/) of the Flask documentation? Just use a `app.test_client()` object to run URL paths against your app. – Martijn Pieters Jul 29 '15 at 20:13

3 Answers3

24

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
5

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)

Raydel Miranda
  • 13,825
  • 3
  • 38
  • 60
3

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.

José María
  • 790
  • 13
  • 24