django-rest-frameworkweb-api-testing

Building and running automated test on User Profile API endpoint but getting error message


I’m creating as well as running an automated test for Profile Page API endpoint using the DRF extension APITestCase.

I already created/ran two separate automated tests for the login/registration API endpoints with no errors in the console. I was trying to set up the test for this API similar to the other two tests.

But when I run python manage.py test again for the HomeownerProfileAPITest in test_profile.py, I get the following error

I’m not sure if I did something wrong in my views, tests. But here are some important files that might make this clearer:

serializers.py

from rest_framework import serializers
from django.contrib.auth import authenticate
from .models import User, HomeownerUser
from .search_indexes import ArboristCompanyIndex, ServiceTypeIndex
from drf_haystack.serializers import HaystackSerializer


class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(style={'input_type': 'password'}, write_only=True)

    class Meta:
        model = User
        fields = ['email', 'username', 'password']
        extra_kwargs = {
            'password': {'write_only': True}
        }
        
    def validate(self, valid):
        if valid['password'] != valid['password']:
            raise serializers.ValidationError({"password": "Passwords do not match."})
        return valid

    def create(self, validated_data):
        user = User.objects.create_user(
            email=validated_data['email'],
            username=validated_data['username'],
            password=validated_data['password']
        )
        return user

class LoginSerializers(serializers.ModelSerializer):
    email = serializers.CharField(max_length=255)
    password = serializers.CharField(
        label =("Password"),
        style={'input_type': 'password'},
        trim_whitespace=False,
        max_length=128,
        write_only=True
    )

    def validate(self, data):
        email = data.get('email')
        password = data.get('password')

        if email and password:
            user = authenticate(request=self.context.get('request'),
                                email=email, password=password)

            if not user:
                msg = ('Invalid email/password')
                raise serializers.ValidationError(msg, code = 'Authorization')
        else:
            msg = ('Must include valid "email" and "password".')
            raise serializers.ValidationError(msg, code = 'Authorization')

        data['user'] = user
        return data
    
    
class HomeownerProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = HomeownerUser
        fields = ['profile_pic', 'street_address', 'homeowner_city', 'homeowner_state', 'homeowner_zip_code']


class ServiceTypeSerializer(HaystackSerializer):
    class Meta:
        index_class = [ServiceTypeIndex]
        fields = ['text', 'name']


class CompanySerializer(HaystackSerializer):
 
    class Meta:
        index_class = [ArboristCompanyIndex]
        fields = [
            'text', 'company_name', 'company_city', 'company_state'
        ]

views


@authentication_classes([JWTAuthentication])
class LoginView(APIView):
     authentication_classes = (JWTAuthentication)
def post(self, request, *args, **kwargs):
        serializer = LoginSerializers(data=request.data, context={'request': request})
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        login(None, user)
        token = Token.objects.create(user=user)
        return Response({"status": status.HTTP_200_OK, "Token": token.key})

        if user is not None:

            # Generate token
            refresh = RefreshToken.for_user(user)
            access_token = str(refresh.access_token)
            return Response({
                'message': 'Successful login',
                'access_token': access_token,
                'refresh_token': str(refresh),
            }, status=status.HTTP_200_OK)
        return Response({'error': 'Invalid email/password'}, status=status.HTTP_401_UNAUTHORIZED)



@authentication_classes([JWTAuthentication])
class RegisterView(APIView):  
    authentication_classes = (JWTAuthentication)
def post(self, request):
    
     serializer = RegisterSerializer(data=request.data, context={'request': request})
     if serializer.is_valid():
        serializer.save()
        return Response({
        'message': 'successfully registered',  
      }, status=status.HTTP_201_CREATED)
     return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@authentication_classes([JWTAuthentication])
class HomeownerProfileView(APIView):
    permission_classes = [DjangoModelPermissionsOrAnonReadOnly]
    authentication_classes = (JWTAuthentication)
    serializer_class = HomeownerProfileSerializer
    def get(self):
        username = self.kwargs['username']
        obj = get_object_or_404(User, username=username)
        return obj

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)
    
    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

urls (app)

from django.urls import path
from arborfindr.views import index
from .views import services_info
from . import views
from django.contrib.auth import views as auth_views
from django.conf import settings
from django.conf.urls.static import static
from .views import RegisterView, LoginView, HomeownerProfileView
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

from rest_framework import routers

app_name = 'arborfindr'

#router = routers.DefaultRouter
#router.register('company/search', CompanySearchView, base_name='company-search')

urlpatterns = [
path('register/', RegisterView.as_view(), name='register'),  # Register API endpoint url
path('login/', LoginView.as_view(), name ='login'), # Login API endpoint url
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
path('update_password/', views.update_password, name='update_password'),
path('api/users/<user_id>/profile/', HomeownerProfileView.as_view()),
path('services/', services_info),
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('arborfindr/', index),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

And finally test_profile.py

from rest_framework.test import APITestCase
from ..models import User
from django.test import TestCase, Client


class HomeownerProfileTest(APITestCase):
    def setUp(self):
        
        self.user = User.objects.create_user(username='test_homeowner', password='password321')
        
    def test_profile(self):
        client = APIClient()
        client.login(username='test_homeowner', password='password321')
        response = client.get(f'api/users/{self.user.id}/profile')
        self.assertEqual(response.status_code, status.HTTP_200_OK)

error traceback

  show_sunset_warning()
System check identified no issues (0 silenced).
.FE
======================================================================
ERROR: test_register_user (arborfindr.test.test_register.UserRegisterTests.test_register_user)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/coreyj/Documents/ArborHub/MyProject/arborfindr/test/test_register.py", line 12, in test_register_user
    response = self.client.post(url, data, format='json')
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/test.py", line 295, in post
    response = super().post(
        path, data=data, format=format, content_type=content_type, **extra)
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/test.py", line 209, in post
    return self.generic('POST', path, data, content_type, **extra)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/test.py", line 233, in generic
    return super().generic(
           ~~~~~~~~~~~~~~~^
        method, path, data, content_type, secure, **extra)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/django/test/client.py", line 676, in generic
    return self.request(**r)
           ~~~~~~~~~~~~^^^^^
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/test.py", line 285, in request
    return super().request(**kwargs)
           ~~~~~~~~~~~~~~~^^^^^^^^^^
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/test.py", line 237, in request
    request = super().request(**kwargs)
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/django/test/client.py", line 1092, in request
    self.check_exception(response)
    ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/django/test/client.py", line 805, in check_exception
    raise exc_value
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper
    return view_func(request, *args, **kwargs)
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
           ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/views.py", line 497, in dispatch
    self.initial(request, *args, **kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/views.py", line 415, in initial
    self.check_permissions(request)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/views.py", line 331, in check_permissions
    for permission in self.get_permissions():
                      ~~~~~~~~~~~~~~~~~~~~^^
  File "/home/coreyj/Documents/ArborHub/MyProject/.venv/lib64/python3.13/site-packages/rest_framework/views.py", line 278, in get_permissions
    return [permission() for permission in self.permission_classes]
                                           ^^^^^^^^^^^^^^^^^^^^^^^
TypeError: 'BasePermissionMetaclass' object is not iterable

======================================================================
FAIL: test_profile (arborfindr.test.test_profile.HomeownerProfileTest.test_profile)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/coreyj/Documents/ArborHub/MyProject/arborfindr/test/test_profile.py", line 18, in test_profile
    self.assertEqual(response.status_code, 200)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 404 != 200

----------------------------------------------------------------------
Ran 3 tests in 0.936s

FAILED (failures=1, errors=1)
Destroying test database for alias 'default'...

test_register_user

from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from ..models import User


# Unit test for user registration API endpoint
class UserRegisterTests(APITestCase):
    def test_register_user(self):
        url = reverse('arborfindr:register')
        data = {'email': 'myemail@mail.com', 'username': 'jonedoe12', 'password': 'mypassword123'}
        response = self.client.post(url, data, format='json')
        self.assertTrue(User.objects.filter(username='jonedoe12').exists())
        user = User.objects.get(username='jonedoe12')
        self.assertEqual(user.email, 'myemail@mail.com')
        self.assertTrue(user.check_password('mypassword123'))
        self.assertEqual(response.status_code, status.HTTP_200_OK)

As you can see, it's throwing a status error message. I added the code for the 200 status like I did with the other tests


Solution

  • You don't have to pass "user_id" into HomeownerProfileTest.test_profile, Just set it up in "setUp", "setUpTestData", or inside "test_profile"

    class HomeownerProfileTest(APITestCase):
    
        def setUp(self):
            # you can set user_id here for multiple testcase if needed
            self.user = User.objects.create_user(username='test_homeowner', password='password321')
    
        def test_profile(self):
            client = Client()
            client.login(username='test_homeowner', password='password321')
            response = client.get(f'api/users/{self.user.id}/profile/')
            self.assertEqual(response.status_code, 200)