A guide to testing flask applications using unittest!
Tags: python, showdev, testing, tutorial
Published: 2020-11-02 14:14:08

Flask is a backend web framework for python. One that is beloved by many python developers! But to make a great flask webapp, testing is important. I noticed the lack of a good library to integrate with the stdlib `unittest` for testing flask apps. So I made one myself!

Check out [`flask-unittest`](https://github.com/TotallyNotChase/flask-unittest) on github!

This tutorial will demonstrate how to test flask applications using the `FlaskClient` object, i.e in an API centric way, *and* also demonstrate how to test flask applications live, using a headless browser!

If you're looking to use a `FlaskClient` to test your app, I recommend you to read through [the official testing guide](https://flask.palletsprojects.com/en/1.1.x/testing/). It's super simple and should only take 5 minutes to read through!

# Test using a `FlaskClient` object
The library provides us with the testcase `ClientTestCase`, which creates a `FlaskClient` object from a `Flask` object for *each test method*, that you can use to test your app. It also provides direct access to flask globals like `request`, `g`, and `session`!

Let's see how you could use `ClientTestCase`

```py
import flask_unittest

from flask_app import create_app

class TestFoo(flast_unittest.ClientTestCase):
    # Assign the flask app object
    app = create_app()

    def test_foo_with_client(self, client):
        # Use the client here
        # Example request to a route returning "hello world" (on a hypothetical app)
        rv = client.get('/hello')
        self.assertInResponse(rv, 'hello world!')
```
We have a flask app in a module named `flask_app`, which has a function to create and return the `Flask` app object. Just need to assign the returned object to `app`.

Remember, you don't *need* a function to create and return the app. As long as you have a correctly configured app object, you can simply assign it!

Now, we define a test method `test_foo_with_client` with 2 parameters. The mandatory `self` and a parameter named `client`. For each test method, `ClientTestCase` will create a `FlaskClient` by using [`.test_client`](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.test_client) and pass it to the test method.

Now you can freely use this to make API calls to your app! In our example, we make a request to `/hello`, which is a simple route returning `hello world!` as a response. You can now use `assertInResponse`, which is a utility method provided by `flask-unittest`, to check if `hello world!` actually exists in the response! (Note: you can also just use `assert 'hello world!' in rv.data` for the same effect)

Inside this test method, you also have access to flask globals like `request`, `g`, and `session`.
```py
def test_foo_with_client(self, client):
    rv = client.get('/hello?q=paramfoo')
    self.assertEqual(request.args['q'], 'paramFoo')    # Assertion succeeds
    # Do a POST request with valid credentials to login
    client.post('/login', data={'username': 'a', 'password': 'b'})
    # Our flask app sets userId in session on a successful login
    self.assertIn('userId', session)    # Assertion succeeds
```
This is obviously very useful for testing!

You can also use the `setUp` method to login to your webapp, and the session will persist in the actual test method! Because `setUp`, `tearDown` and the test method are ran together in a set - using the same `FlaskClient`. The next test method along with its `setUp` and `tearDown` methods, however, will use a brand new `FlaskClient` - and hence a new `session`.

```py
def setUp(self, client):
    # Login here
    client.post('/login', data={'username': 'a', 'password': 'b'})

def test_foo_with_client(self, client):
    # Check if the session is logged in
    self.assertIn('userId', session)    # Assertion succeeds

def tearDown(self, client):
    # Logout here, though there isn't really a need - since session is cleared for the next test method
    client.get('/logout')
```

There are also multiple utility methods for common assertions!

* `assertStatus` - Assert the status code of a response returned from a `client` API call.
* `assertResponseEqual` - Assert the response `.data` is equal to the given string
* `assertJsonEqual` - Assert the response `.json` is equal to the given dict
* `assertInResponse` - Assert given string/bytestring exists in response `.data`
* `assertLocationHeader` - Assert the location header in response is equal to the given string. Useful for redirect requests.

Check out a full example of using this testcase [in the repo](https://github.com/TotallyNotChase/flask-unittest/blob/master/tests/flask_client_test.py)

Would you like the Flask app to be built per test method too? Instead of having it as a constant property of the class? Check out [`AppClientTestCase`](https://github.com/TotallyNotChase/flask-unittest#test-using-both-flask-and-flaskclient) (or even [`AppTestCase`](https://github.com/TotallyNotChase/flask-unittest#test-using-flask) if you don't need the functionality of the `FlaskClient`)

# Test a live flask server using `selenium`
What if you want to just run a regular flask server live and use a headless browser like `selenium` to test it out? `LiveTestCase` and `LiveTestSuite` is for you!

```py
import flask_unittest
from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

from flask_app import create_app

class TestFoo(flask_unittest.LiveTestCase):
    driver: Union[Chrome, None] = None
    std_wait: Union[WebDriverWait, None] = None

    @classmethod
    def setUpClass(cls):
        # Initiate the selenium webdriver
        options = ChromeOptions()
        options.add_argument('--headless')
        cls.driver = Chrome(options=options)
        cls.std_wait = WebDriverWait(cls.driver, 5)

    @classmethod
    def tearDownClass(cls):
        # Quit the webdriver
        cls.driver.quit()

    def test_foo_with_driver(self):
        # Use self.driver here
        # You also have access to self.server_url and self.app
        # Example of using selenium to go to index page and try to find some elements (on a hypothetical app)
        self.driver.get(self.server_url)
        self.std_wait.until(EC.presence_of_element_located((By.LINK_TEXT, 'Register')))
        self.std_wait.until(EC.presence_of_element_located((By.LINK_TEXT, 'Log In')))
```

This is pretty simple, we instantiate the driver in `setUpClass`, use it as we would normally in the test methods and quit it in `tearDownClass`.

You'll have access to the flask app as well as the url the app is running on (localhost + port) in the testcase. (only in the instance methods though)

The real juice of this is actually in `LiveTestSuite`. Unlike the previous testcases, which can be run using the regular `unittest` testsuite, or simply doing `unittest.main()` - `LiveTestCase` **requires** you to use `LiveTestSuite`
```py
# Pass the flask app to suite
suite = flask_unittest.LiveTestSuite(create_app())
# Add the testcase
suite.addTest(unittest.makeSuite(TestFoo))
# Run the suite
unittest.TextTestRunner(verbosity=2).run(suite)
```
We have to pass the flask app object to `LiveTestSuite`, so it can spawn it using `.run` - this same `app` object will be available in the testcase.

The flask server will be spawned as a daemon thread once the suite starts running and it runs for the duration of the program.

Check out a full example of using this testcase [in the repo](https://github.com/TotallyNotChase/flask-unittest/blob/master/tests/flask_live_test.py)!

For more examples, including a full emulation of the [official flask testing example](https://flask.palletsprojects.com/en/1.1.x/testing/), check out the [repo](https://github.com/TotallyNotChase/flask-unittest)!