0

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

`

7
  • You mentioned a problem with the create_all but haven't added how it is invoked. I suppose it's called in `create_app', can you add this function to your question for us to see? Commented Jun 13, 2017 at 11:33
  • db.create_all() does get called in the setUp() method above, but your question got me to look again at create_app() in the main package. It turns out that it also calls db.create_all(), as you guessed. I've added it to the question. Commented Jun 13, 2017 at 16:02
  • ... but AFAIK create_all() should be idempotent. Commented Jun 13, 2017 at 16:10
  • There was a typo. I've been interested in create_app (not create_all) function. If your models.py import a db, or create instance inside itself, you should try changing lines in create_app() to from .models import User,Player,Game,Match,Team,Post, db as models_db and models_db.create_all() so you'll be creating tables with db from your models.py Commented Jun 13, 2017 at 16:30
  • You nailed it! The problem was in the initialisation order of app and db. The db I was importing from the base package (and calling create_all() on) was not actually the one bound to the models. Hence create_all() created nothing. Commented Jun 14, 2017 at 8:00

2 Answers 2

2

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()
Sign up to request clarification or add additional context in comments.

Comments

0

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)

  • loads the right config into app.config (including the right SQLACHEMY_DATABASE_URL)
  • imports the model classes and db (as model_db) from models.py

This 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?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.