简体   繁体   中英

How to avoid authentication dependencies in FastAPI during testing

This might be a newbie question, but I can't get dependency_overrides to work for testing.
Following the docs this should be simple to implement but I'm missing something…
Consider the following code:

In main.py:

from fastapi import FastAPI
from routes import router

app = FastAPI()
app.include_router(router)

In routes.py:

from fastapi import APIRouter, status
from fastapi.param_functions import Depends
from fastapi.responses import JSONResponse
from authentication import Authentication

router = APIRouter()

@router.get("/list/", dependencies=[Depends(Authentication(role=user))])
async def return_all():
    response = JSONResponse(
        status_code=status.HTTP_200_OK,
        content="Here is all the objects!"
    )
    return response

In test_list.py:

from unittest import TestCase
from fastapi.testclient import TestClient
from main import app
from authentication import Authentication

def override_dependencies():
    return {"Some": "Thing"}

client = TestClient(app)
app.dependency_overrides[Authentication] = override_dependencies

class ListTestCase(TestCase):
    def test_list_get(self):
        response = client.get("/list/")
        self.assertEqual(200, response.status_code)

Gives the following error:

self.assertEqual(200, response.status_code)
AssertionError: 200 != 403

ie, it tried to authenticate but was denied. Hence, it doesn't seem that it overrides my dependency.

Note that Depends is used in the path operation decorator ( @router.get ), and not the function as in the docs…

I see that you are using Authentication(role=user) as a dependency, but then you are trying to override Authentication and these are two different callables, the former being actually Authentication(role=user).__call__ ; thus I guess FastAPI is not able to match the correct override.

The problem with this approach is that Authentication(role=user).__call__ is an instance method, but in order to override a dependency in dependency_overrides you must be able to adress the callable statically, so that it is always the same every time you call it.

I had to implement a similar thing, and i solved this by injecting the authentication logic in like this:

class RestIdentityValidator:
    methods: List[AuthMethod]

    def __init__(self, *methods: AuthMethod):
        self.methods = list(dict.fromkeys([e for e in AuthMethod] if methods is None else methods))

    def __call__(
            self,
            bearer_token_identity: IdentityInfo | None = Depends(get_bearer_token_identity),
            basic_token_identity: IdentityInfo | None = Depends(get_basic_token_identity)
    ) -> IdentityInfo | None:
        identity = None
        for auth_method in self.methods:
            match auth_method:
                case AuthMethod.BEARER:
                    identity = bearer_token_identity
                case AuthMethod.BASIC:
                    identity = basic_token_identity
                case _:
                    pass
            if identity is not None:
                break
        return identity

and then used it like this:

@router.get("/users", response_model=Response[List[UserOut]])
async def get_all_users(
        identity: IdentityInfo = Depends(RestIdentityValidator(AuthMethod.BEARER)),
...

Then you can inject and override this normally. The fact that you are using it in the decorator shouldn't make any difference in this case.

Now this sample is not implementing the same functionality as yours, but I hope it can give you a hint on how to solve your problem.

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