pytest-flask icon indicating copy to clipboard operation
pytest-flask copied to clipboard

how to testing flask-login?

Open tanyewei opened this issue 9 years ago • 22 comments

how to testing flask-login?

tanyewei avatar Jan 25 '16 02:01 tanyewei

What do you mean?

On Mon, 25 Jan 2016 5:38 am tanyewei [email protected] wrote:

how to testing flask-login?

— Reply to this email directly or view it on GitHub https://github.com/vitalk/pytest-flask/issues/40.

vitalk avatar Jan 25 '16 07:01 vitalk

I guess he has trouble with cookies because client doesn't have the expected scope. You can use a fixture to make an authentication and call this fixture in all your test functions for an authenticated user

SBillion avatar Feb 16 '16 16:02 SBillion

trouble with cookies because client doesn't have the expected scope

What is the expected scope? You can always explicitly create required scope by passing any headers into the client. Example of using get_auth_token method from Flask-Login documentation:

@pytest.fixture
def user():
    """Should return an user instance."""


@pytest.fixture
def credentials(user):
    return [('Authentication', user.get_auth_token())]


def test_endpoint(client, credentials):
    res = client.get(url_for('endpoint'), headers=credentials)

vitalk avatar Feb 16 '16 17:02 vitalk

If there's nothing else I'm closing this issue. @tanyewei feel free to reopen it.

vitalk avatar Feb 24 '16 15:02 vitalk

I suspect he's having trouble with login_user.

Say you have a fixture like this:

@pytest.fixture
def logged_in_user(request, test_user):
    flask_login.login_user(test_user)
    request.addfinalizer(flask_login.logout_user)

And a test like this:

@pytest.mark.usefixtures('logged_in_user')
def test_protected(client):
    resp = client.get('/protected')
    assert resp.status_code == 401

Because the pytest-flask test client pushes a new context, the flask_login.current_user proxy ends up returning the anonymous user and any tests that expect a logged-in user fail.

tmehlinger avatar Feb 26 '16 22:02 tmehlinger

@tmehlinger thank you for clarifications.

For now request context has been pushed to ensure the url_for can be used inside tests without any configuration. The same feature can be achieved if the SERVER_NAME is set and application context has been pushed. If this behaviour is appropriate and doesn't break anything, then your issue can be fixed.

vitalk avatar Feb 27 '16 00:02 vitalk

@vitalk, you're welcome. :)

@tanyewei, the way I would solve your problem is by disabling authentication when you're running unit tests. You could run any tests that explicitly require login functionality with a live application server using the live_server fixture.

tmehlinger avatar Feb 27 '16 00:02 tmehlinger

hi, everybody. I am facing the same problem. @tmehlinger . do you mean disable the view's auth while testing? to me this is really unexcepted. I sometime need to query something in the view throught the user id. for example get the user's order-list. annoy user would pass the view any way!!!

@vitalk, I am not really got what you mean. but I find that, use client.post('login') would work. but login_user(user) fails. I am new to flask, really confuse now. the false code: 2016-06-17-12 20 56-screenshot

the success code: 2016-06-17-12 21 12-screenshot

hackrole avatar Jun 17 '16 04:06 hackrole

Hi @hackrole!

As mentioned above, the client fixture pushes a new request context, so the 1st example doesn’t work because the current_user is anonymous. The alternate approach is explicitly pass propper headers to client (as per https://github.com/vitalk/pytest-flask/issues/40#issuecomment-184774553)

vitalk avatar Jun 17 '16 08:06 vitalk

As of flask-login release 0.4.0 the get_auth_token() function has been removed.

matt-sm avatar Jul 14 '17 05:07 matt-sm

Struggling with this as well - Is there a method or setup I can do to make sure the client does a post to the login part of our site such as client_authenticated

shepherdjay avatar Nov 15 '18 23:11 shepherdjay

Hi guys,

I was also struggling with testing with authenticated user for flask-login, and here is my working snippet:

@pytest.fixture()
def test_with_authenticated_user(app):
    @login_manager.request_loader
    def load_user_from_request(request):
        return User.query.first()

doanguyen avatar Dec 13 '18 17:12 doanguyen

I was struggling with this too. My solution ended up being to dynamically overwrite the login_manager.request_loader and returning the user I want to be authenticated when calling the protected endpoint.

def test_authentication(app, client):
    with app.test_request_context():
        test_user = User.get(username=USERS['testuser']['username'])

        @app.login_manager.request_loader
        def load_user_from_request(request):
            return test_user

        resp = client.get('/auth/request-token/')

WARNING: Be careful with this approach if you share the app between multiple tests - like when you are using a fixture with scope session, as the overwritten login_manager.request_loader will not get reset. One of my tests was failing somewhat randomly when using pytest-xdist and it took me a while to realize that it is due to the 'residual' of the login_manager.request_loader

In the end when I wanted to do an unauthenticated call after overwriting the '@app.login_manager.request_loader' to return the test user, I needed to overwrite it again, so it doesn't return anything, making it work for the unauthenticated user scenario:

    @app.login_manager.request_loader
    def load_user_from_request(request):
        return None

zoltan-fedor avatar Feb 28 '19 22:02 zoltan-fedor

I did something similar to @zoltan-fedor but with a slight twist. Here are the relevant parts:

@pytest.fixture
def app():
    app = create_app('TestConfig')
    with app.app_context():
        yield app


@pytest.fixture
def authenticated_request(app):
    with app.test_request_context():
        # Here we're not overloading the login manager, we're just directly logging in a user
        # with whatever parameters we want. The user should only be logged in for the test,
        # so you're not polluting the other tests.
        yield flask_login.login_user(User('a', 'username', 'c', 'd'))


@pytest.mark.usefixtures("authenticated_request")
def test_empty_predicates():
    # The logic of your test goes here

stevenmanton avatar Aug 07 '19 16:08 stevenmanton

Someone solutions? I 'm same problem!! =(

johndiego avatar Nov 22 '19 09:11 johndiego

Hi all, since I am struggling with the issue as well, which is the best way to approach it? I need to test some features which are available just to some user levels so disabling auth is a no go for me. Thanks!

libremente avatar Mar 17 '20 20:03 libremente

The way I understand the guidelines, as documented here, the suggested approach (don't know if it's the best one) was already mentioned by @stevenmanton

Namely, if this is your view:

@login_required
def secured_view():
    arg = request.get_json()['arg']
    ...

your test would, within a request context, manually log in a user and then call the view function

def test_secured_view():
    with app.test_request_context("route", json={"arg": "val"}):
        flask_login.login_user(User(...))
        secured_view()

unfortunately, you can't use the client fixture because it simulates a full request, meaning you can't control the request context it generates. Hope that helps.

avikam avatar Mar 19 '20 21:03 avikam

I can't get this to work. I want to run my test like a regular user (see only the user data, denied access to some views). Usually, I use:

from slots_tracker_server import app as flask_app

@pytest.fixture(scope="session", autouse=True)
def client():
    flask_client = flask_app.test_client()

to initialize my app and DB.

Why cann't I use something like:

flask_login.login_user(Users.objects().first())

or

flask_client.login_user(Users.objects().first())

in order to simulate a user?

also, I'm using

@app.before_request

in order to force most views to be protected.

can you please help? thanks.

shlomiLan avatar Apr 12 '20 21:04 shlomiLan

I hit this too, in tests trying to simulate an AJAX client that first hits a GET /csrf endpoint to fetch a CSRF token that it then includes in a CSRF header when making the desired request. To work around this, I ended up creating my own client fixture with a longer scope (e.g. "module" or "session" both work) rather than using pytest-flask, which doesn't currently allow customization of its client fixture's scope. In case this helps anyone else!

jab avatar Nov 22 '20 22:11 jab

The fixture scope mentioned by @jab is the cause of the incorrect login status.

I have a complete example here, the problem lies in the cleanup work done in the app fixture:

@pytest.fixture
def app():
    print('Hit')
    _app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'

    with _app.app_context():
        db.create_all()
    yield _app

    os.remove('test.db')

In this case, the default fixture scope is 'function', which means that each test case will be re-executed once, If you use pytest -s, you can see that Hit is output three times, which is equal to the number of test cases, that is, each case is executed once.

So that the database is rebuilt , the data created in other places is lost, so the user cannot be queried here, so the status is not logged in yet.

To fix it, just modify the value of a larger scope, such as session, package or module:

@pytest.fixture(scope='package')
#@pytest.fixture
def app():
    _app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'

    with _app.app_context():
        db.create_all()
    yield _app

    os.remove('test.db')

dongweiming avatar Feb 24 '22 00:02 dongweiming