26

I am currently trying to write some unit tests for my Flask application. In many of my view functions (such as my login), I redirect to a new page. So for example:

@user.route('/login', methods=['GET', 'POST'])
def login():
    ....
    return redirect(url_for('splash.dashboard'))

I'm trying to verify that this redirect happens in my unit tests. Right now, I have:

def test_register(self):
    rv = self.create_user('John','Smith','John.Smith@myschool.edu', 'helloworld')
    self.assertEquals(rv.status, "200 OK")
    # self.assert_redirects(rv, url_for('splash.dashboard'))

This function does make sure that the returned response is 200, but the last line is obviously not valid syntax. How can I assert this? My create_user function is simply:

def create_user(self, firstname, lastname, email, password):
        return self.app.post('/user/register', data=dict(
            firstname=firstname,
            lastname=lastname,
            email=email,
            password=password
        ), follow_redirects=True)
funnydman
  • 9,083
  • 4
  • 40
  • 55
Jason B
  • 7,097
  • 8
  • 38
  • 49

4 Answers4

33

Flask has built-in testing hooks and a test client, which works great for functional stuff like this.

from flask import url_for, request
import yourapp

test_client = yourapp.app.test_client()
with test_client:
    response = test_client.get(url_for('whatever.url'), follow_redirects=True)
    # check that the path changed
    assert request.path == url_for('redirected.url')

For older versions of Flask/Werkzeug the request may be available on the response:

from flask import url_for
import yourapp

test_client = yourapp.app.test_client()
response = test_client.get(url_for('whatever.url'), follow_redirects=True)

# check that the path changed
assert response.request.path == url_for('redirected.url')

The docs have more information on how to do this, although FYI if you see "flaskr", that's the name of the test class and not anything in Flask, which confused me the first time I saw it.

Bryce Guinta
  • 3,456
  • 1
  • 35
  • 36
Rachel Sanders
  • 5,734
  • 1
  • 27
  • 36
  • 6
    I strongly encourage people to use this solution, which removes Flask-Testing dependency (in general, I think using as few extensions as possible is a good thing, or only the "stable" and "big" ones, as Flask-Principal, Flask-Security, etc.). However, this solution raises a `RuntimeError: Attempted to generate a URL without the application context being pushed. This has to be executed when application context is available`, because `url_for` needs application context. All you have to do is putting it inside a `with app.app_context():` – Edouard Berthe Nov 27 '16 at 05:46
  • 6
    "response.request" --> AttributeError: 'Response' object has no attribute 'request' – Calaf Feb 01 '19 at 00:56
  • 1
    not sure what ```response.request``` is, but if you keep the request context by using ```with app.test_client() as c:```, then you can just import request and url_for and do ```request.path == url_for(...``` – DyRuss Mar 14 '19 at 15:50
  • 1
    From my tests, I don't think that using `request.path` would work because the path is set to the initial URL and not to the URL after the redirect. @DyRuss – Florent Dec 10 '19 at 22:24
  • From my tests I can confirm using client as a context (`with client`) does change the `request` context when `client.get` is called (and follow_redirects=True). Thanks @DyRuss – Bryce Guinta Jul 21 '20 at 22:22
  • The answer to the following question solved this for me... https://stackoverflow.com/questions/60111814/flask-application-was-not-able-to-create-a-url-adapter-for-request – Ben Oct 19 '22 at 19:32
11

Try Flask-Testing

there is api for assertRedirects you can use this

assertRedirects(response, location)

Checks if response is an HTTP redirect to the given location.
Parameters: 

    response – Flask response
    location – relative URL (i.e. without http://localhost)

TEST script:

def test_register(self):
    rv = self.create_user('John','Smith','John.Smith@myschool.edu', 'helloworld')
    assertRedirects(rv, url of splash.dashboard)
kartheek
  • 6,434
  • 3
  • 42
  • 41
11

One way is to not follow the redirects (either remove follow_redirects from your request, or explicitly set it to False).

Then, you can simply replace self.assertEquals(rv.status, "200 OK") with:

self.assertEqual(rv.status_code, 302)
self.assertEqual(rv.location, url_for('splash.dashboard', _external=True))

If you want to continue using follow_redirects for some reason, another (slightly brittle) way is to check for some expected dashboard string, like an HTML element ID in the response of rv.data. e.g. self.assertIn('dashboard-id', rv.data)

broox
  • 3,538
  • 33
  • 25
-1

You can verify the final path after redirects by using Flask test client as a context manager (using the with keyword). It allows keeping the final request context around in order to import the request object containing request path.

from flask import request, url_for

def test_register(self):
    with self.app.test_client() as client:
        user_data = dict(
            firstname='John',
            lastname='Smith',
            email='John.Smith@myschool.edu',
            password='helloworld'
        )
        res = client.post('/user/register', data=user_data, follow_redirects=True)
        assert res.status == '200 OK'
        assert request.path == url_for('splash.dashboard')