Paradoxis
Paradoxis

Reputation: 4708

Fire multiple requests in a flask test request context

I'm currently writing unit tests for a flask app that requires a session to be kept open during the test. For this I found out I can use test_request_context. This works fine until I need to make multiple requests.

My current test deals with a login/logout system and I need to ensure that certain properties are actually reset from the session when the user clicks the logout button.

A rough example of my code:

from unittest import TestCase
from flask import Flask, session
from mypackage import auth_blueprint


class FlaskTestCase(TestCase):
    def setUp(self):
        self.app = Flask(__name__)
        self.app.secret_key = urandom(24)
        self.app.register_blueprint(auth_blueprint)   # The blueprint to test
        self.client = self.app.test_client()

    def test_login_logout():
        with self.app.test_request_context("/auth/login", data={"username": "foo", "password": "bar"}, method="POST"):
            assert session["logged_in"]
            # How would I make A call to logout?
            # self.client.post("/auth/logout") doesn't work
            assert not session["logged_in"]

Does anyone know how I could make multiple calls and inspect the same session in one test? Thanks for the help in advance

Upvotes: 1

Views: 2460

Answers (2)

Ayoub Ennassiri
Ayoub Ennassiri

Reputation: 4866

You can use the setUpClass and tearDownClass. You first need to define a global TestCase that would be called in your separate test files.

The example that I'm giving is using connexion. You can adapt it by updating the setUpClass definition and defining only the app as a Flask app.

test_server.py

import unittest
import os
import connexion
import logbook

from config import db
from models import Book


# Logging configuration
log = logbook.Logger(__name__)

# Books Fixtures
BOOKS = [
    {
        'name': 'Book 1',
    },
    {
        'name': 'Book 2',
    },
    {
        'name': 'Book 3'
    },
]


class TestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        basedir = os.path.abspath(os.path.dirname(__file__))
        log.info("setUpClass")
        # Setting UP the DB and the tests
        cls.connex_app = connexion.App(__name__, specification_dir=basedir)
        cls.connex_app.add_api('swagger.yml', validate_responses=True)
        cls.connex_app.app.config['TESTING'] = True
        cls.connex_app.app.config['WTF_CSRF_ENABLED'] = False
        cls.connex_app.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'test.db')
        # Delete DB if exists : not for production
        if os.path.exists('test.db'):
            os.remove('test.db')
        db.create_all()
        TestCase.add_sample_algorithm()
        TestCase.add_sample_backtest()
        cls.test_client = cls.connex_app.app.test_client()

    @staticmethod
    def add_sample_book():
        # Iterate over the BOOKS Dictionary and populate the DB :
        for book in BOOKS:
            book_to_add = Book(name=book['name'])
            log.info("## Book to add:" + str(book_to_add))
            db.session.add(book_to_add)

        db.session.commit()

    @classmethod
    def tearDownClass(cls):
        log.info("tearDownClass ")
        db.session.remove()
        db.drop_all()
        if os.path.exists('test.db'):
            os.remove('test.db')


if __name__ == '__main__':
    unittest.main()

Then you can call in each test file the test server which will create a test db, run a test client and then tear down once the battery of tests is finished. The setUpClass and tearDownClass defined this way will avoid you having issues (Blueprint when calling the test server from multiple test files (e.g attributeError: a name collision occurred between blueprints).

from test_server import TestCase


class BookTest(TestCase):
    @classmethod
    def setUpClass(cls):
        super(BookTest, cls).setUpClass()
        # Additional initialization

    @classmethod
    def tearDownClass(cls):
        super(BookTest, cls).tearDownClass()

    def test_get_root(self):
        resp = self.test_client.get('/')
        self.assertEqual(404, resp.status_code)

    def test_get_all_books(self):
        resp = self.test_client.get('/api/books')
        self.assertEqual(200, resp.status_code)
        self.assertEqual(3, len(resp.json))

        # Book 1 :
        self.assertEqual(resp.json[0]['name'], "Book 1")

Thanks,

Ayoub

Upvotes: 0

Tejas
Tejas

Reputation: 304

You should be able to use the same context for both calls. I have tried this with flask_restful and it works. You can read up on application contexts and how they are passed along. Also, you could try using the current_app context by doing this:

from flask import current_app
current_app.app_context()

I would try this first:

from unittest import TestCase
from flask import Flask, session
from mypackage import auth_blueprint


class FlaskTestCase(TestCase):
    def setUp(self):
        self.app = Flask(__name__)
        self.app.secret_key = urandom(24)
        self.app.register_blueprint(auth_blueprint)   # The blueprint to test
        self.client = self.app.test_client()
        self.app.app_context().push()


    def test_login_logout():
        with self.app.app_context():
            self.client.post("/auth/login", data={"username": "foo", "password": "bar"})
            assert session["logged_in"]
            # How would I make A call to logout?
            self.client.post("/auth/logout")
            assert not session["logged_in"]

Upvotes: 4

Related Questions