Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sessions are empty when testing #69

Open
vinyll opened this issue Aug 11, 2017 · 7 comments
Open

Sessions are empty when testing #69

vinyll opened this issue Aug 11, 2017 · 7 comments
Assignees

Comments

@vinyll
Copy link

vinyll commented Aug 11, 2017

This doc expresses that we can access session without any extra context manager.

However the following code does not return any session key (session is empty):

from flask import Flask


import pytest
from flask import session


myapp = Flask('testing')
myapp.secret_key = 'secret'
myapp.debug = True

@myapp.route('/')
def home():
    return session['message']


@pytest.fixture
def app():
    return myapp


def test_home(client):
    session['message'] = 'something'
    response = client.get('/')
    assert response.data == 'something'

Any hint on what we're missing here?

Thanks :)

@yokotoka
Copy link

Confirm! I can't work with session - it's always empty.

@timorthi
Copy link

timorthi commented Feb 25, 2018

+1, sessions are also coming up empty for me.

For modifying sessions in your tests you're expected to use session_transaction():

...

def test_home(client):
    with client.session_transaction() as session:
        session['message'] = 'something'
    response = client.get('/')
    assert response.data == 'something'

However, oddly enough, it appears that for values set in the view function you'll have to do the same, so it's not clear to me whether this is a bug with Flask or pytest-flask:

...

@myapp.route('/')
def home():
    session['ping'] = 'pong'
    return 'hello world'

def test_home(client):
    response = client.get('/')

    with client.session_transaction() as session:
        assert session['ping'] == 'pong' # This works

    assert session['ping'] == 'pong' # This doesn't work (session is empty)

@sorgloomer
Copy link

I can't get sessions working, it raises an error when using session_transaction:

    def test_csrf_protection_right_token(client, app):
        with client.session_transaction() as session:
            token = app.jinja_env.globals['csrf_token']()
            header = app.jinja_env.globals['csrf_header']()
            import logging
            import flask
            logging.warn("in test")
            logging.warn(flask.session.items())
>           resp = client.post('/my_endpoint', headers=[(header, token)])

...

E           AssertionError: Popped wrong request context.  (<RequestContext 'http://localhost/' [GET] of app_test> instead of <RequestContext 'http://localhost/' [GET] of app_test>)

@russmac
Copy link

russmac commented Jan 20, 2019

Was also having this issue until I moved my yield client outside of the with client.session_transaction() as session: enumeration. You can debug by printing the session object, Which shows the secure cookie contents.

Wont work:

def client():
    with app.test_client() as client:
        with client.session_transaction() as session:
            session['Authorization'] = 'redacted'
            print(session) # will be empty SecureCookieSession
            yield client

Works:

def client():
    with app.test_client() as client:
        with client.session_transaction() as session:
            session['Authorization'] = 'redacted'
        print(session) # will be populated SecureCookieSession
        yield client

@micahaza
Copy link

@russmac you've saved my day, thanks.

@northernSage northernSage self-assigned this Mar 31, 2021
@northernSage
Copy link
Member

northernSage commented Mar 31, 2021

Thanks @vinyll for taking the time to open the issue and all others for providing examples and a working solution. I feel like the issue could use a bit more info on why the described problem is happening.

While pytest-flask pushes a test request context for you at the beginning of the test session as stated in the docs

During test execution a request context will be automatically pushed for you, so context-bound methods can be conveniently called (e.g. url_for, session).

pytest-flask does not prevent flask from creating and pushing new request context objects onto the request context stack upon request handling. What this means and its consequences can be seen with help of the code snippet bellow:

def _context_info(scope, session):
    """shows current session and request context
       objects ids for debugging purposes"""
    print(scope, id(session._get_current_object()),
        " - session[message]: ",
        session.get("message", "not-found"),
        " - _request_ctx_stack top: ", 
        id(_request_ctx_stack.top))

def test_client_session(client, live_server):
    # adds a simple view that writes to session object
    @live_server.app.route('/home')
    def home():
        session["message"] = "BACON"
        _context_info("session id inside view: ", session)
        return "session updated"

    session["message"] = "EGGS"

    # checks context state before and after request
    _context_info("session id before request: ", session)
    response = client.get(url_for("home"))
    _context_info("session id after request: ", session)

    # pops top request object off stack
    print("poping top request context from stack...")
    _request_ctx_stack.pop()

    # check what is on top of stack
    _context_info("session id after popping request: ", session)

    assert False

Output:

...
----------------------------- Captured stdout call -----------------------------
session id before request:         139747393971024  - session[message]:   EGGS  - _request_ctx_stack top:  139747394620304
session id inside view:            139747393972272  - session[message]:  BACON  - _request_ctx_stack top:  139747394052432
session id after request:          139747393972272  - session[message]:  BACON  - _request_ctx_stack top:  139747394052432
popping top request context from stack...
session id after popping request:  139747393971024  - session[message]:   EGGS  - _request_ctx_stack top:  139747394620304

From this we can see that flask is pushing a new request context into the stack, shadowing the session object of the test request context pushed by pytest-flask and this seems to be the reason for the problem. The solution provided by @russmac is the official flask way of getting around this problem. A secondary possible solution would be using a view function (similar to my example above) to write to the session object and then access it using session["yourkey"] within your test since the session object will still be reachable:

def test_session(client, live_server):
    
    # temporary view just to alter session
    @live_server.app.route('/setsession')
    def set_session():
        session["message"] = "EGGS"
        return session["message"]
    
    response = client.get(url_for("set_session"))

    assert response.status == "200 OK"
    assert session["message"] == "EGGS"

@behai-nguyen
Copy link

@vinyll thank you for this question. 5 years less 2 days later, I had this issue. And a HUGE thank to @russmac for the solution.

Please allow me add another observation... Or am I doing something wrong?

My original app() and test_client() fixtures which resulted in the reported problems:

@pytest.fixture(scope='module')
def app():
    app = create_app( get_config(name='test') )

    app.app_context().push()

    # Global db.
    db.init_app( app )
    from book_keeping.utils import context_processor

    return app

    # clean up / reset resources here

@pytest.fixture(scope='module')
def test_client( app ):
    # Create a test client using the Flask application configured for testing
    with app.test_client() as testing_client:
        yield testing_client  # this is where the testing happens!

The updated test_client() fixture as per @russmac's solution:

@pytest.fixture(scope='module')
def test_client( app ):
    # Create a test client using the Flask application configured for testing
    with app.test_client() as testing_client:
        """
        See: https://github.com/pytest-dev/pytest-flask/issues/69 
		Sessions are empty when testing #69 
        """
        with testing_client.session_transaction() as session:
            session['Authorization'] = 'redacted'

        yield testing_client  # this is where the testing happens!

I am keeping

session['Authorization'] = 'redacted'

in @russmac's honour 😄

And following is a test method:

@pytest.mark.timesheet_bro
def test_close( app ):
    bro_obj = TimesheetBRO( 1 )

    #
    # RuntimeError: Working outside of request context.
	#
    # session[ 'user_id' ] = 100
    #

    with app.test_request_context( '?searchType={}'.format(UNRESTRICT_SEARCH_TYPE) ):
        assert request.args[ 'searchType' ] == UNRESTRICT_SEARCH_TYPE
        assert request.values.get( 'searchType' ) == UNRESTRICT_SEARCH_TYPE

        session[ 'user_id' ] = 1

        data = bro_obj.close( 326 )

    assert bro_obj.last_message == ''
    assert data['status']['code'] == HTTPStatus.OK.value

I have to access the session within:

with app.test_request_context():

Otherwise I will get the error:

RuntimeError: Working outside of request context.

Thank you and best regards.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants