Reputation: 15
I'm writing unit tests for a REST API written in Flask with the flask_sqlalchemy extension. Because I have a number of model classes, I wrote a TestCase
subclass to do the standard setUp/cleanUp of the test database. All my test classes inherit from this. Each test succeeds when run alone, but when I run more than one test in a single class, the second setUp() fails on the self.db.session.commit()
(I'm trying to add an entry to the User table) because self.db.create_all()
has (silently) failed to create any tables.
Here is my base test class, in the __init__.py
of the test package:
import unittest
from .test_client import TestClient
from .. import create_app
from pdb import set_trace as DBG
class ApiTest(unittest.TestCase):
default_username = 'fred'
default_password = 'bloggs'
db = None
def setUp(self):
try:
self.app = create_app('testing')
self.addCleanup(self.cleanUp)
self.ctx = self.app.app_context()
self.ctx.push()
from .. import db
self.db = db
self.db.session.commit()
self.db.drop_all(app=self.app)
from ..models import User, Player, Team, Match, Game
# self.app.logger.debug('drop_all())')
self.db.create_all(app=self.app)
# self.app.logger.debug('create_all())')
user = User(user_name=self.default_username)
user.password = self.default_password
self.db.session.add(u)
self.db.session.commit()
self.client = TestClient(self.app, user.generate_auth_token(), '')
except Exception, ex:
self.app.logger.error("Error during setUp: %s" % ex)
raise
def cleanUp(self):
try:
self.db.session.commit()
self.db.session.remove()
self.db.drop_all(app=self.app)
# self.app.logger.debug('drop_all())')
self.ctx.pop()
except Exception, ex:
self.app.logger.error("Error during cleanUp: %s" % ex)
raise
Can anyone tell me what's wrong here please?
EDIT: Added the code for create_app()
as requested.
# chessleague/__init__.py
import os
from flask import Flask, g
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from . import config
app = None
db = None # The database, initialised in create_app()
def create_app(config_name):
app = Flask(__name__)
app.config.update(config.get_config(config_name))
# if app.config['USE_TOKEN_AUTH']:
# from api.token import token as token_blueprint
# app.register_blueprint(token_blueprint, url_prefix='/auth')
import logging
from logging.handlers import SysLogHandler
syslog_handler = SysLogHandler()
syslog_handler.setLevel(logging.WARNING)
app.logger.addHandler(syslog_handler)
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
global db
db = SQLAlchemy(app)
db.init_app(app)
from .models import User,Player,Game,Match,Team,Post
db.create_all()
from .api import api as api_blueprint
app.register_blueprint(api_blueprint, url_prefix='/chessleague')
return app
`
Upvotes: 0
Views: 1483
Reputation: 2154
create_all()
applies to the metadata, that is being discovered by importing modules with models. In your case, models' metadata binds to the db
from your models.py
but you are calling create_all()
from chessleague/__init__.db
from create_app()
function, which is different objects for SqlAlchemy. You can fix that by using db
from models.py
:
from .models import User,Player,Game,Match,Team,Post, db as models_db
models_db.create_all()
Upvotes: 2
Reputation: 15
Here's the initialisation sequence that worked for me - comments welcome!
My test class setUp()
calls create_app(config_name)
from the main app package.
The main app package(__init__.py
) creates the app instance at module level, ie app=Flask(my_app_package_name)
Then my function
create_app(config_name)
db
(as model_db
) from models.pyThis import creates the symbol db
at module level in models.py, followed by the model class definitions:
# models.py
from . import app
db = SQLAlchemy(app)
...
class User(db.Model)
...
etc
Now everything is set up properly: the symbol 'db' can be imported anywhere from models.py, and I can call db.create_all()
successfully from my test setUp()
.
@Fian, can you post your solution as an answer so I can give you credit?
Upvotes: 0