3

We are extending the Flask-cli with some custom commands. The command test is one of them:

# run.py: specified by FLASK_APP
# This is the Flask application object
app = create_app(os.getenv('FLASK_ENV') or 'default')

@app.cli.command()
def test():
    """Run the unit tests."""
        
    tests = unittest.TestLoader().discover('tests')

    test_runner = unittest.TextTestRunner()
    test_runner.run(tests)

However a typical test (using Python's built-in unittest module) looks like this which is based on the style described here.

# some-tests.py: unittest-based test case.
class SomeTestCase(unittest.TestCase):
    def setUp(self):
        self.app = create_app('testing')
        self.app_context = self.app.app_context()
        self.app_context.push()

    def tearDown(self):
        self.app_context.pop()

    def test_load(self):
        pass

I am clearly hitting an anti-pattern here: I have initialized a flask object with the default(development) configuration because I need it for the @app.cli.command() decorator which all happens in run.py. However once I run the test setUp function in some-tests.py I somehow have to obtain a Flask object utilizing the testing configuration, e.g. by recreating a Flask app with the testing configuration like what happens now.

I would like to have pointers on how one goes about to implement a flask-cli test command in which only one Flask object is created which is reused amongst the various test cases without having the need of explicitely setting the environment to testing before I run flask test on the command line.

Community
  • 1
  • 1
Yunus King
  • 1,141
  • 1
  • 11
  • 23

1 Answers1

4

I'm not sure if this answer will suit your requirements but that is how I would try to approach this problem. Unfortunately, if you want to use default CLI interface in Flask than you need to call create_app just to call flask test command. What you can do is try use pytest. It allows you to create fixtures that can be used across multiple test cases. For example, in your tests package create file named conftest.py and declare some default fixtures like this:

@pytest.fixture
def app():
    return create_app('testing')


@pytest.fixture
def client(app):
    return app.test_client()


@pytest.fixture
def database(app):
    _db.app = app

    with app.app_context():
        _db.create_all()

    yield _db

    _db.session.close()
    _db.drop_all()

Then in your test case file (ex. test_login.py) you can use those fixtures like this:

# Notice argument names are the same as name of our fixtures
# You don't need to import fixtures to this file - pytest will  
# automatically recognize fixtures for you
def test_registration(app, client):
    response = client.post(
        '/api/auth/login',
        json={
            'username': 'user1',
            'password': '$dwq3&jNYGu'
        })
    assert response.status_code == 200
    json_data = response.get_json()
    assert json_data['access_token']
    assert json_data['refresh_token']

The best thing about this approach is that you don't need to create setUp and tearDown methods. Then you can create test cli command for your app:

import pytest

@app.cli.command()
def test():
    '''
    Run tests.
    '''
    pytest.main(['--rootdir', './tests'])

And call it like this flask test.

devaerial
  • 2,069
  • 3
  • 19
  • 33