I am writing an application with a global object that should live as long as the application as alive. Different endpoints should mutate the global object.
Below is my server with an example mock-up object to be mutated on dummy endpoint calls.
server.py
#!/usr/bin/env python3
from flask import Flask, g, request
class Foo(object):
def __init__(self):
self.bar = None
def add_bar(self, bar):
if self.bar is not None:
raise Exception("You blew it!")
self.bar = bar
def create_app():
app = Flask(__name__)
return app
app = create_app()
def get_foo():
foo = getattr(g, '_foo', None)
if foo is None:
print("foo is None. Creating a foo")
foo = g._foo = Foo()
return foo
@app.teardown_appcontext
def teardown_foo(exception):
foo = getattr(g, '_foo', None)
if foo is not None:
print("Deleting foo")
del foo
@app.route("/add_bar", methods=['POST'])
def bar():
bar = request.form.get("bar")
foo.add_bar(bar)
return "Success"
if __name__ == "__main__":
app = create_app()
app.run('localhost', port=8080)
Like a good software developer, I want to test this. Here's my test suite:
test.py
#!/usr/bin/env python3
from server import *
import unittest
class FlaskTestCase(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
print("Creating an app...")
self.app = create_app()
print("Created an app...")
with self.app.app_context():
self.foo = get_foo()
def tearDown(self):
del self.foo
def test_add_bar(self):
with self.app.test_client() as client:
client.post("/add_bar", data={'bar': "12345"})
assert self.foo.bar == "12345"
if __name__ == "__main__":
unittest.main()
When I run the test, I notice that my foo
object is never deleted (by the prints). Here's the output when I run the test suite:
13:35 $ ./test.py
Creating an app...
Created an app...
foo is None. Creating a foo
F
======================================================================
FAIL: test_add_bar (__main__.FlaskTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "./test.py", line 21, in test_add_bar
assert self.foo.bar == "12345"
AssertionError
----------------------------------------------------------------------
Ran 1 test in 0.009s
FAILED (failures=1)
I must be doing something incorrectly with my global object with respect to the application context. I've pored over the testing documents, but cannot seem to find exactly what I need.
How can I ensure my foo
object is destroyed when the application context goes out of scope (like in my tests)? Why isn't my unit test suite mutating its foo
object that is created on setUp
?
I thought it could be modifying the global app
object within server.py
, but when I printed foo
, I found that it wasn't defined within the scope of my test suite.
The problem was an interference between the global app
object within the server.py
and the app being created within the original test.py
. Specifically, since the app was being imported, its lifetime was the life of the test suite.
I removed the global app
object by using flask.Blueprint
. Here's the final server.py
:
#!/usr/bin/env python3
from flask import Flask, g, request, Blueprint
main = Blueprint('main', __name__)
def get_foo():
foo = getattr(g, 'foo', None)
if foo is None:
foo = g.foo = Foo()
return foo
@main.before_request
def before_request():
foo = get_foo()
@main.route("/add_bar", methods=['POST'])
def bar():
bar = request.form.get("bar")
g.foo.add_bar(bar)
return "Success"
class Foo(object):
def __init__(self):
self.bar = None
def add_bar(self, bar):
if self.bar is not None:
raise Exception("You blew it!")
self.bar = bar
def create_app():
app = Flask(__name__)
app.register_blueprint(main)
@app.teardown_appcontext
def teardown_foo(exception):
if g.foo is not None:
del g.foo
return app
if __name__ == "__main__":
app = create_app()
with app.app_context():
foo = get_foo()
app.run('localhost', port=8080)
And here's the final test.py
:
#!/usr/bin/env python3
from server import create_app, get_foo
import unittest
class FlaskTestCase(unittest.TestCase):
def setUp(self):
self.app = create_app()
self.app_context = self.app.app_context()
self.app_context.push()
self.client = self.app.test_client()
self.foo = get_foo()
def tearDown(self):
self.app_context.pop()
del self.foo
def test_add_bar_success(self):
assert self.foo.bar is None
self.client.post("/add_bar", data={'bar': "12345"})
assert self.foo.bar == "12345"
def test_foo_reset_on_new_test(self):
assert self.foo.bar is None
if __name__ == "__main__":
unittest.main()
test_foo_reset_on_new_test
illustrates that the foo
object associated with the test suite is reset on each test.
All tests pass.