Wednesday, October 11, 2023

Resend verification link email API endpoint

Now that the endpoint for handling clicks of the verification link is done, the next should be an endpoint that resends this verification link. This is necessary in case the user did not click on the verification link before it expired. The code for this is fairly simple, but I stumbled onto another issue which will also be a part of this blog post.

class ResendVerificationEmailView(APIView):
    '''Resending verification email to user'''

    def get(self, *args, **kwargs):
        user_id = self.kwargs['user_id']
        try:
            user_obj = User.objects.get(id=user_id)
        except Exception as e:
            return Response(
                data=str(e),
                status=status.HTTP_400_BAD_REQUEST
            )
        send_verification_link_email(user_obj)
        user_data = UserSerializer(user_obj)
        return Response(
            data=user_data.data,
            status=status.HTTP_200_OK
        )

The test for this endpoint is quite simple:

def test_resend_verification_endpoint(mock_send_email, test_user):
    '''Testing endpoint for resending verification email'''
    client = APIClient()

    api_response = client.get(
        '/user/resend-verification/{user_id}'.format(user_id=test_user.id),
        format='json'
    )
    assert api_response.status_code == 200

    old_user_id = test_user.id
    test_user.delete()
    api_response = client.get(
        '/user/resend-verification/{user_id}'.format(user_id=old_user_id),
        format='json'
    )
    assert api_response.status_code == 400

The test creates a user and makes a GET request with the user ID. If the user does not exist, it should return a 400 response, or else it should return a 200 response. As before with testing the user registration, I do not want to be sending an email or even trying to send one. Except that the mocking of the send email method is done in a fixture mock_send_email which appears in the test function argument.

import pytest

@pytest.fixture
def mock_send_email(monkeypatch):
    monkeypatch.setattr(
        'user_auth.views.send_verification_link_email',
        lambda user: print(
            'Sending email to {username}'.format(username=user.username)
        ),
        raising=True
    )

The change made is in the method being mocked – user_auth.views.send_verification_link_email. To begin with, the entire path is being used to make it explicit which method is being mocked. However, the method send_verification_link_email is not being mocked in utils.py module where it is defined but rather in views.py module where it is being used. Here is the difference with respect to mocking in JavaScript. In JavaScript, one would create a spy that would replace the original object with a spy. In Python, one does not replace the original object but rather merely replaces the reference of the object where it is being used. So the original email method in utils.py is not being mocked, but rather the usage of this method in views.py is being mocked.

For convenience this mock fixture is placed in a separate fixtures.py file as it can also be used in the test for user registration and so keep the code DRY.

No comments:

Post a Comment