fastapi icon indicating copy to clipboard operation
fastapi copied to clipboard

How to properly mock AuthJWT (or any other injected dependencies)

Open dmsfabiano opened this issue 1 year ago • 1 comments

First Check

  • [X] I added a very descriptive title to this issue.
  • [X] I used the GitHub search to find a similar issue and didn't find it.
  • [X] I searched the FastAPI documentation, with the integrated search.
  • [X] I already searched in Google "How to X in FastAPI" and didn't find any information.
  • [X] I already read and followed all the tutorial in the docs and didn't find an answer.
  • [X] I already checked if it is not related to FastAPI but to Pydantic.
  • [X] I already checked if it is not related to FastAPI but to Swagger UI.
  • [X] I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

  • [X] I commit to help with one of those options 👆

Example Code

from typing import Dict

from fastapi import FastAPI, HTTPException, Depends, Request
from fastapi.responses import JSONResponse
from fastapi_jwt_auth import AuthJWT
from fastapi_jwt_auth.exceptions import AuthJWTException

from ml_api.api.mongo.user import UserLayer

app = FastAPI()
user_db = UserLayer()


@AuthJWT.load_config
def get_config():
    return AuthSettings(



@app.post('/get_tokens')
def get_tokens(user: User, Authorize: AuthJWT = Depends()):
    if not user_db.is_valid_user(user.username, user.password):
        raise HTTPException(status_code=401, detail="Bad username or password")

    return {"access_token": Authorize.create_access_token(subject=user.username),
            "refresh_token": Authorize.create_refresh_token(subject=user.username)}

# ---------------- test -------------------------------

import asyncio
import sys
from unittest.mock import MagicMock

from fastapi.testclient import TestClient
from fastapi_jwt_auth import AuthJWT
from pytest import fixture

from ml_api.api.main import app


@fixture(scope='function')
def test_client():
    if sys.platform == "win32" and sys.version_info >= (3, 8, 0):
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    app.dependency_overrides[AuthJWT] = MagicMock()
    return TestClient(app)

@fixture(scope='function')
def test_authorize_mock(test_client):
    return test_client.app.dependency_overrides[AuthJWT]

@fixture(scope='package')
def test_get_tokes_request():
    return {
        "username": "test_username",
        "password": "test_password"
    }

class TestGetTokens:
    def test_on_valid_user(self, mocker, test_client, test_get_tokes_request, test_authorize_mock):
        user_db_mock = mocker.patch(f"{BASE_PATH}.user_db")
        user_db_mock.is_valid_user.return_value = True

        response = test_client.post('/get_tokens', json=test_get_tokes_request)

        assert_that(response.status_code, is_(equal_to(HTTPStatus.OK)))
        assert_that(response.json(), is_(equal_to({
            "access_token": test_authorize_mock.create_access_token.return_value,
            "refresh_token": test_authorize_mock.create_refresh_token.return_value
        })))
        user_db_mock.is_valid_user.assert_called_once_with(
            test_get_tokes_request["username"],
            test_get_tokes_request["password"]
        )

Description

  • I am trying to successfully mock AuthJWT, so that instead of executing the external depencies of it (.create_access_token for example) I can use MagicMock to simply validate that the functiosn themselves are being called
  • When the pytest test runs, I expect it to return 200, and the test to pass
  • However, the test fails with 422 with an error of "field missing, value error", Even though that the the correct request is being sent to the Starlette test client
  • I suspect mocking AuthJWT through the dependency_overrides may be causing some internal issues with the Dependency injection, making FastAPI think the endpoint requires more parameters

I am sure I missed something, or did something incorrectly, so I am mainly looking for any hints or the right way to do it!

Operating System

Windows

Operating System Details

No response

FastAPI Version

0.78.0

Python Version

3.8.0

Additional Context

No response

dmsfabiano avatar Aug 10 '22 01:08 dmsfabiano

Please see this issue for solution and explanation https://github.com/tiangolo/fastapi/issues/3331

JarroVGIT avatar Aug 10 '22 02:08 JarroVGIT

That is super helpful @JarroVGIT - Thank you ! Even though that this allows me to succesfuly mock AuthJWT, it does not quite allow me to control or manipulate the return of the mock

def test_client(test_authorize_mock):
    if sys.platform == "win32" and sys.version_info >= (3, 8, 0):
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    app.dependency_overrides[AuthJWT] = lambda: test_authorize_mock
    return TestClient(app)

@fixture(scope='function')
def test_authorize_mock():
    return MagicMock()

regardless of how I manipulate test_authorize_mock the response from the endpoint it's always

{
    "access_token": {},
    "refresh_token" {}
}

instead of

{
    "access_token": "",
    "refresh_token": ""
}

even if I explicitly do this on my test

test_authorize_mock.create_access_token.return_value = ""

dmsfabiano avatar Aug 10 '22 20:08 dmsfabiano

I am sorry for not being more helpful but I have limited experience myself with MagicMock(). I wouldn't know how to manipulate it for your test cases, I just remembered the issue I mentioned and why MagicMock() caused some issues there, but anything else is above my level of knowledge.

JarroVGIT avatar Aug 11 '22 10:08 JarroVGIT