Reputation: 4708
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
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
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