I'm following the basic testing conventions from here http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vii-unit-testing
It's worked beautifully up till the point where I needed to add some postgres views and functions to my database, db.create_all() obviously has no notion of them.
I am using Alembic (with Flask-Migrate, more thanks to you! ) so conceptually I should just be able to run an upgrade but I'm having trouble figuring out how to do it programmatically.
So instead of db.create_all() in TestCase.setup I have this:
migrate = Migrate(self.app, db)
config = Config("migrations/alembic.ini")
config.set_main_option("script_location", "migrations")
command.upgrade(config, "head")
But when that tries to run env.py via alembic I get this:
File "migrations/env.py", line 20, in <module>
config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI'))
File "/Users/atli/work/server/env/lib/python2.7/site-packages/werkzeug/local.py", line 338, in __getattr__
return getattr(self._get_current_object(), name)
File "/Users/atli/work/server/env/lib/python2.7/site-packages/werkzeug/local.py", line 297, in _get_current_object
return self.__local()
File "/Users/atli/work/server/env/lib/python2.7/site-packages/flask/globals.py", line 34, in _find_app
raise RuntimeError('working outside of application context')
RuntimeError: working outside of application context
I must admit I'm not quite familiar enough w. Flask internals to know what that means.
The corresponding lines in migrations/env.py:
from flask import current_app
config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata
I realise this is not really an issue for Flask-Migrate per se but it would be interesting nonetheless to know how best to integrate Flask-Migrate with basic Flask testing procedures?
@atlithorn After reading http://flask.pocoo.org/docs/0.10/appcontext/ i realized that the flask migrate code assumes an application context is around by using from flask import current_app
You can explicitly set what the current_app is with:
with app.app_context():
I assume in your case you have set
self.app = application.application.test_client()
then you could do
with application.application.app_context():
config.set_main_option("script_location", "migrations")
command.upgrade(config, "head")
Do you really need to run migrations for testing? The testing database is disposable, correct? Since you are making a brand new one each time you run your tests, you can create it with db.create_all() and avoid the migration hassle. See this example.
Even though it's a hassle I find running all the migrations to create the database a better test because it's closer to what will happen to create the database in production, if you only ever used db.create_all() in your test suite then you could have forgot to run python manage.py db migrate and the tests would pass.
@miguelgrinberg we did that at first and it worked perfectly. Things got complicated as the app grew and we now have a bunch of stored procedures that we need to test against as well.
Our stored procedures are generated by the migrations so currently we have to remember to update the stored procedure in 2 places - in the migrations and in the tests.
(I don't think our current method of maintaining the stored procedures is optimal and would be interested to hear how you or others have approached this)
Decided to do some digging and found this - definitely a better approach than our current one.
https://bitbucket.org/zzzeek/sqlalchemy/wiki/UsageRecipes/Views
With Flask-Migrate you can trigger an upgrade programmatically with the upgrade function. See https://github.com/miguelgrinberg/flasky/blob/master/manage.py#L71.
Note that you need an app context, in the example I referenced Flask-Script provides the context.
@lee101 I like that approach. As the migrations get more complicated I'd like to have a least one test that instantiates and migrates the DB from start to finish.
Does anyone have an example of how to do this with Flask-Migrate within the unit tests? Would be much appreciated.
@TiernanKennedy Here's a quick snippet I have from one of my apps (Python 3.5):
import unittest
from contextlib import contextmanager
from unittest.mock import patch
from <your_app_entry_point> import main
@contextmanager
def env(env_dict, clear=True):
with patch.dict("os.environ", env_dict, clear=clear):
yield
class TestCLI(unittest.TestCase):
def test_db_migration(self):
with env(ENV_KEY1=env_value_1, ...):
sys.argv = [<cli_entry_point_cmd>, "db", "upgrade"]
with self.assertRaises(SystemExit) as exit:
main()
assert exit.exception.code == 0
It's fairly basic and simple: I'm only testing that the exit code is 0 (no tests on stderr / stdout) and I already assume that there is a migrations directory containing the migration scripts. The env contextmanager is used to ensure I control all the environment variables present during test invocation, but it's actually optional. You can run the test without it, but then it will be more susceptible to actual system environment variables.
This issue will be automatically closed due to being inactive for more than six months. Seeing that I haven't responded to your last comment, it is quite possible that I have dropped the ball on this issue and I apologize about that. If that is the case, do not take the closing of the issue personally as it is an automated process doing it, just reopen it and I'll get back to you.
I stumbled upon this while trying to answer the same question. With the most recent version of Flask and Flask Migrate this is my approach to solving the problem.
In the base of my git repository, I have a shell script called "doTest.sh". "doTest.sh" sets the FLASK_APP variable to "test_app.py" to tell Flask Migrate what the entry point script is. In the create_app method, I pass the environment of "test". This tells the application to use the "test" database settings. When doTest.sh executes, "flask db upgrade" it will run all the migrations. Then after it executes that it executes my test.
I am not sure if there is a better way to prepare this or not, but searching Stackoverflow and google I haven't really found something that is current.
doTest.sh
#!/bin/sh
export FLASK_APP="test_app.py"
flask db upgrade
python -m unittest app/tests/tests_models.py
test_app.py
from app import create_app
# Instanciate and initialize the app creation
app = create_app('test')
# Start the app
if __name__ == "__main__":
app.run()
Most helpful comment
Even though it's a hassle I find running all the migrations to create the database a better test because it's closer to what will happen to create the database in production, if you only ever used
db.create_all()in your test suite then you could have forgot to runpython manage.py db migrateand the tests would pass.