pytest-dev / pytest-flask

A set of pytest fixtures to test Flask applications

Home Page:http://pytest-flask.readthedocs.org/en/latest/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

teardown_request called twice during test

opened this issue · comments

repoduce code example

import pytest
from flask import Flask


def create_app():
    app = Flask(__name__)

    @app.route("/hello")
    def hello():
        return "Hello world"

    @app.teardown_request
    def teardown_request_func(error=None):
        print("teardown_request is running!")

    return app


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


def test_hello(client):
    response = client.get('/hello')
    assert response.status_code == 200

then execute py.test -s
the test got output like the following

platform darwin -- Python 3.7.6, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
rootdir: /Users/eruda/scripts/fsk
plugins: pylama-7.7.1, flask-1.0.0, cov-2.5.1, pylava-0.2.2
collected 1 item                                                                                                             

test_healthz.py .teardown_request is running!
teardown_request is running!
In [3]: pytest_flask.__version__                                                                                              
Out[3]: '1.0.0'

was i doing something wrong ? thanks in advance.

Take this with a grain of salt, but I suspect this is happening because of the second RequestContext that pytest-flask creates and pushes in order to expose url_for, session , etc. in your test scope. From flask docs on teardown_request we see that it

Register a function to be run at the end of each request, ....
These functions are executed when the request context is popped...

So I think your teardown code is being executed the first time upon conclusion of client.get('/hello') and a second time when the context pushed by pytest-flask is popped.

Just adding some code to backup my previous guess

pytest_flask.plugin._push_request_context

@pytest.fixture(autouse=True)
def _push_request_context(request):
   # (snip)...
    ctx = app.test_request_context()
    # let's add this so we can identify this request context instance
    ctx.session = "pytest-flask-session"  
    ctx.push()

Your example, only tweaked a little bit

def create_app():
   # (snip)...
    @app.route("/hello")
    def hello():
        # let's add this so we can identify this request context instance
        _request_ctx_stack.top.session = "hello_view_context"
        return "Hello world"
    # (snip)...

Running pytest:

tests/double_teardown.py .teardown_request is running for context hello_view_context
teardown_request is running for context pytest-flask-session

From that we can see we have two separate request contexts and your teardown code is running once for each context.

So I don't think you are doing anything wrong (unless there's some unmentioned/unknown side effect I'm not seeing). I'll close the issue for the time being.

Thanks