简体   繁体   中英

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

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 ?

@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.

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):
    # ...

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='test@test.com').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

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"))

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

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