vitalik / django-ninja

💨 Fast, Async-ready, Openapi, type hints based framework for building APIs
https://django-ninja.dev
MIT License
7.15k stars 426 forks source link

Mock Authentication for a view #1066

Open AryanGHM opened 8 months ago

AryanGHM commented 8 months ago

Hello! I was trying to rewrite some of my tests involving API calls through Django's TestClient and I couldn't figure out how to mock the authentication callable.

I've written a method to validate the supplied token with a remote server (a CAS-like service), this is how I handle authentications on all of my other projects but I used to set a test token for testing purposes and use that to test the API and authentication, but this causes tests to be slow due to the latency of the requests going back and forth to the authentication service. So I wanted to mock the authentication callable instead to bypass the whole authentication flow altogether.

core/api.py

from ninja import Router
from users.auth import user_auth

router = Router(auth=user_auth)

@router.get("/_health", tags=["Health Check"])
def health(request: HttpRequest):
    """
    Health check doesn't check any functionality it only validates that the server is up.
    """
    return {"status": "healthy"}

users/auth.py

def user_auth(request):
    # does authentication either raises an HttpError or returns a CustomUser instance

tests/test_api.py this is not the actual test although I've also tried running exactly this and still no luck.

class TestMocks(TestCase):
    @patch("core.api.user_auth")
    def test_patch(self, mock):
        response = self.client.get(
            path=reverse_lazy("api-v0.1.0:health"),
        )
        mock.assert_called() # <-- this always fails 

I couldn't find any specific documentation in the official documents about how to mock the authentication callable passed to a router instance. I tried patching it using unittest.mock.patch decorator but that didn't work.

So my question is that, is there a way to do this? And if not, what is the correct way to bypass the authentication while testing the API operations?

Thanks in advance.

santigandolfo commented 8 months ago

In my case what I ended up doing is creating a function:

def get_auth_bearer():
    return AuthBearer()
api = NinjaAPI(auth=get_auth_bearer())

And then for my tests I patched it like this:

@pytest.fixture
def api_client(mocker):
    mocker.patch('core.authentication.get_auth_bearer', return_value=None)
    yield APIClient()

Could you try something similar? In you case I think it would be something similar to this:

def get_auth_bearer():
    return user_auth

router = Router(auth=get_auth_bearer())

And then in your tests:

class TestMocks(TestCase):
    @patch("core.api.get_auth_bearer", return_value=None)
    def test_patch(self, mock):
        response = self.client.get(
            path=reverse_lazy("api-v0.1.0:health"),
        )
        mock.assert_called()
AryanGHM commented 8 months ago

I will try this as soon as I can, thank you very much for the help.