flask-jwt-extended: Fake Authorization Header during testing (pytest)
Asked Answered
I

5

15

This is the function I wish to test

@jwt_required
    def get_all_projects(self):
        # implementation not included here

I call the function from a pytest class

def test_get_all_projects(db_session):
    all_projects = ProjectController.get_all_projects()

with the db_session fixture

@pytest.fixture(scope='function')
def db_session(db, request):
    """Creates a new database session for a test."""
    engine = create_engine(
                            DefaultConfig.SQLALCHEMY_DATABASE_URI,
                            connect_args={"options": "-c timezone=utc"})
    DbSession = sessionmaker(bind=engine)
    session = DbSession()
    connection = engine.connect()
    transaction = connection.begin()
    options = dict(bind=connection, binds={})
    session = db.create_scoped_session(options=options)
    db.session = session

    yield session

    transaction.rollback()
    connection.close()
    session.remove()

This result in the error

>           raise NoAuthorizationError("Missing {} Header".format(header_name))
E           flask_jwt_extended.exceptions.NoAuthorizationError: Missing Authorization Header

../../.virtualenvs/my-app/lib/python3.6/site-packages/flask_jwt_extended/view_decorators.py:132: NoAuthorizationError

Manually Calling create_access_token

I still get the same result when I call create_access_token in the fixture above

db.session = session
session._test_access_token = create_access_token(identity='pytest')

yield session

How can I fake JWT tokens during testing with pytest?

Inoculum answered 20/10, 2017 at 10:2 Comment(0)
F
21

@jwt_required only works in the context of a Flask request. You can send in the access token using the flask test client with the headers name option:

def test_foo():
    test_client = app.test_client()
    access_token = create_access_token('testuser')
    headers = {
        'Authorization': 'Bearer {}'.format(access_token)
    }
    response = test_client.get('/foo', headers=headers)
    # Rest of test code here

Optionally, you could unwrap the decorated method by using the __wrapped__ property. In your case, it would look like:

method_response = get_all_projects.__wrapped__()

Note that any calls to the flask-jwt-extended helper functions in your endpoint (such as get_jwt_identity(), current_user, etc). would not work this way, as they require a flask request context. You could get around this by mocking the flask-jwt-extended functions used inside the function, but that may be harder to maintain as the application grows and changes.

Firdausi answered 20/10, 2017 at 15:32 Comment(0)
R
12

One option for faking JWT tokens during unit testing is to patch jwt_required. More specifically patch the underlying function verify_jwt_in_request. This mocks the decorator and removes the need to create authorization tokens for the test.

from unittest.mock import patch


@patch('flask_jwt_extended.view_decorators.verify_jwt_in_request')
def test_get_all_projects(mock_jwt_required):
    # ...
Reciprocal answered 22/8, 2019 at 15:42 Comment(1)
This is the best answer as it answers the underlying question.Dovecote
K
3

Old topic, but here's some additional insight about how to test functions with @jwt_required:

@pytest.fixture(scope="function", autouse=True)
def no_jwt(monkeypatch):
  """Monkeypatch the JWT verification functions for tests"""
  monkeypatch.setattr("flask_jwt_extended.verify_jwt_in_request", lambda: print("Verify"))
Kalliekallista answered 7/6, 2020 at 7:4 Comment(1)
works like a charm. Also, I had to add the Bearer header with a fake token when requesting the resource.Elana
C
2

Here's what i ended up doing and works for me. In conftest.py:

@pytest.yield_fixture(scope='function')
def app():
  _app = create_app(TestConfig)
  ctx = _app.test_request_context()
  ctx.push()

  yield _app

  ctx.pop()

@pytest.fixture(scope='function')
def testapp(app):
    """A Webtest app."""
    testapp = TestApp(app)

    with testapp.app.test_request_context():
        access_token = create_access_token(identity=User.query.filter_by(email='[email protected]').first(), expires_delta=False, fresh=True)
    testapp.authorization = ('Bearer', access_token)

    return testapp

And then in your TestConfig, set the following flags for flask-jwt-extended:

JWT_HEADER_TYPE = 'Bearer'
JWT_BLACKLIST_ENABLED = False
Coimbatore answered 4/12, 2018 at 23:12 Comment(0)
A
0

In my case, I was using the @jwt.user_claims_loader wrapper for admin roles. I was also using cookies for the production side of things. In order to take advantage of the user_claims_loader, I created a test like so:

# conftest.py
from my.app import create_app

@pytest.fixture
def app():
    app = create_app(testing=True)
    app.config['JWT_COOKIE_CSRF_PROTECT'] = False
    app.config['JWT_TOKEN_LOCATION'] = 'json'
    jwt = JWTManager(app)

    add_user_claims_loader(jwt)

    return app

As you can see, I also reset my JWT_TOKEN_LOCATION to json so that it isn't looking for cookies. I created another fixture to create the access token so I could use it across tests

# conftest.py
@pytest.fixture
def admin_json_access_token(app, client):
    access_token = create_access_token({'username': 'testadmin',
                                        'role': 'admin'})
    return {
        'access_token': access_token
    }

And I used it in my tests:

# test_user.py
def test_get_users(app, client, db, admin_json_access_token):
    rep = client.get('/api/v1/users', json=admin_json_access_token)
    assert rep.status_code == 200

As an example of what my resource looked like:

# my/resources/admin/api.py
class Users(Resource):
    @jwt_required
    @admin_required # custom wrapper that checks the claims
    def get(self):
        all_users = User.query.all()
        return all_users, 200
Apollo answered 1/6, 2019 at 5:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.