简体   繁体   中英

How to Completely Teardown Flask App After Each Test in pytest?

I am testing my Flask app using Pytest. I have 3 files

tests/
--conftest.py
--test_one.py
--test_two.py

If I run test_one.py or test_two.py using the following command, it will work without issue.

python -m pytest tests/test_one.py

The issue arises when I try to run all tests with the following command:

python -m pytest tests

I get this error:

AssertionError: View function mapping is overwriting an existing endpoint function: serve_static

Im not actually surprised by this error since init_app(app) gets called twice (once per file) and I don't ever teardown the actual app .

My question is, Is there a way to completely teardown a flask app and rebuild it for each individual test? I am looking for the actual commands to do the tearing down (ie app.teardown() )

Edit: I am open to alternative ways of setting up a Flask app testing environment, if it would solve this problem (without creating new ones).

Edit 2: I found this question. It is similar and would solve my problem but it involves importing inside a function. I suspect there has to be a better way.

conftest.py

import os
import pytest

from app import app, init_app
from config import TestConfig


@pytest.fixture(scope='function')
def client():

    app.config.from_object(TestConfig)

    with app.test_client() as client:
        with app.app_context():
            init_app(app)
        yield client

    try:
        os.remove('tests/testing.db')
    except FileNotFoundError:
        pass

app/__init__.py

app = Flask(__name__)
app.url_map._rules.clear()
db = SQLAlchemy(app)
migrate = Migrate(app, db)

def init_app(app):
    ...

I would create a Class in each of your files which inherits TestCase. This way you can use the function setup() to generate your testclient as a Instance-Variable before each test. This way you can be sure that each test has the same environment to work with.

from unittest import TestCase
from app import app as application

class TestApplication(TestCase):
  def setUp(self):
    self.client = application.app.test_client()

  def test_sth(self):
    r = self.client.get('/approute2sth')
    self.assertEqual(r.status_code, 200)

  def test_sth_else(self):
    r = self.client.get('/approute2sth_else')
    self.assertEqual(r.status_code, 200)

I am thinking of a solution but does not involve tearing down the flask app. I do not prefer tearing it down for every test case, as that would itself lead to increased uptime for every test case ( unless the flask app initialization is unique to every test case )

Since tests run parallely in pytest, you can do the following

try:
    app
except NameError:
    app.config.from_object(TestConfig)
    with app.test_client() as client:
        with app.app_context():
            init_app(app)
else:
    print("App is already configured. Let us proceed with the test case")

You can wrap this around a singleton class instead of taking the above approach as well

Some combination of the following might work. This, I believe, was the old way to do it (though I never did it myself). I was under the impression that pytest uses the concept of fixtures to make direct teardown() unnecessary.

#clear current db session
db.session.remove()
#drop all tables in db
db.drop_all()
#remove app_context
app.app_context.pop()

See this blog post on pytest for more discussion.

Ultimately my problem was how I created my app . Since it was created as a gloval variable in __init__.py , I could never remake it for each test. I refactored __init__.py (and the rest of my code) to use a create_app function where I could create the same app over and over again. The following code ended up working.

app/__init__.py

def create_app(config)
    app = Flask(__name__)
    app.url_map._rules.clear()
    from .models import db
    app.config.from_object(config)
    with app.app_context():

    return app

def init_app(app):
    ...

conftest.py

import os
import pytest

from app import create_app, init_app
from config import TestConfig


@pytest.fixture(scope='function')
def client():
    app = create_app()
    app.config.from_object(TestConfig)

    with app.test_client() as client:
        with app.app_context():
            from app.models import db
            init_app(app, db)
            yield client

    try:
        os.remove('tests/testing.db')
    except FileNotFoundError:
        pass

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM