Search code examples
pythonunit-testingtestingdjango-rest-framework

Django Rest Framework (DRF) authenticate is not working with unit test database?


I have a BaseTestCase class for base set up. Let me show you the problem:

class UserTestCase(BaseTestCase):

    def test_auth_login(self):
    self.client.logout()
    url = "/auth/login/"
    body = {
        "email": self.user_1.email,
        "password": "12345678"
    }
    is_true = self.user_1.check_password("12345678")
    print("password: ", is_true) # OUTPUT : password: True
    response = self.client.post(url, data=body)
    print(response) OUTPUT: {"error": "Given credentials are wrong."}
    self.assertEqual(response.status_code, 200) # FALSE ERROR

And this is my view:

class AuthToken(ObtainAuthToken):
@extend_schema(
    request=serializers.LoginSerializer,
    responses={200: serializers.TokenSerializer, 400: DefaultExceptionSerializer},
)
def post(self, request, *args, **kwargs):
    serializer = serializers.LoginSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    email = serializer.validated_data["email"]  # type: ignore
    password = serializer.validated_data["password"]  # type: ignore
    user = authenticate(username=email, password=password)
    if user:
        token, created = Token.objects.get_or_create(user=user)
        return Response({"token": token.key}, status=status.HTTP_200_OK)

    return Response({"error": "Given credentials are wrong."}, status=status.HTTP_400_BAD_REQUEST)

User variable is coming None and my view is returning error. I don't understand why authenticate(username=email, password=password) is coming None. Its working very well on "runserver".

Additional Information: I override user model. In my settings.py I have AUTH_USER_MODEL = "UserAuth.User" and I don't know is that important but I also have this in my settings.py:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql_psycopg2",
        "NAME": env("DB_NAME"),
        "USER": env("DB_USER"),
        "PASSWORD": env("DB_PASSWORD"),
        "HOST": env("DB_HOST"),
        "PORT": env.int("DB_PORT"),
    },
    'test': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'test_db.sqlite3',

    }
}
TEST_RUNNER = 'django.test.runner.DiscoverRunner'

I know authenticate is not working because

is_true = self.user_1.check_password("12345678")
print("test_auth true", is_true)

is coming True


Solution

  • The error you are getting is not from authenticate. The issue is because the user requesting to login is not registered.

    You must first create a .setUp() method to register the user before you can test the login.

    This is a a better way of how you can write your testcase:

    from rest_framework.test import APITestCase
    
    class UserTestCase(APITestCase):
    
    def setUp(self):
       self.user = User.objects.create_user(email="[email protected]", password="12345678") # Create a user  
    
    def test_auth_login(self):
       
       url = "/auth/login/" # Better use named urls instead of hardcoded urls
       body = {
         "email": "[email protected]",
         "password": "12345678"
       }
    
       response = self.client.post(url, data=body)
       self.assertEqual(response.status_code, 200)
    

    Remember to include any required fields when creating a user.

    My recommendation:

    1. Always use named urls so that you can refer to them easily rather than hard coded urls.
    2. Always keep things simple. In python, simplicity is key.