Friday, September 29, 2023

Testing the register user API endpoint

Writing tests for the API call threw up a few basic errors that I had overlooked along with the fact that I had forgotten to add the confirm password field. The confirm password field will come next, but first the tests and the changes in the code to make the tests pass.

To begin with the most basic test for the API:

from rest_framework.test import APIClient

def test_register_new_user():
    '''Test API to register new user'''
    client = APIClient()

    # Should result in a user created in db
    api_response = client.post(
        '/user/register-user',
        {
            'username': 'someuser@domain.com',
            'password': 'somepass'
        },
        format='json'
    )
    assert api_response.status_code == 201
    assert api_response.data['username'] == 'someuser@domain.com'
    assert hasattr(api_response.data, 'password') == False
    assert api_response.data['is_active'] == False

    users_in_db = User.objects.all().count()
    assert users_in_db == 1
However, even something as this fails. Examining the api_response.data shows Bad request. The reason is that I have been using request.POST for populating the UserSerializer. Since I am using Request from DRF, I do not need to use request.POST as that still contains the encodings with a submitted form. Instead I can use request.data which contains a dictionary with the fields. This way, the API extracts the username and password whether a form was filled with the link accessed in a browser or the data submitted through Postman or in this case the test.

The tests for the API can be expanded to a few other cases:
# Should fail model validation
api_response = client.post(
    '/user/register-user',
    {
        'username': 'someuser',
        'password': 'somepass'
    },
    format='json'
)
assert api_response.status_code == 400

# Should fail because of missing password field
api_response = client.post(
    '/user/register-user',
    {
        'username': 'someuser1@domain.com',
    },
    format='json'
)
assert api_response.status_code == 400

# Should fail because of missing username field
api_response = client.post(
    '/user/register-user',
    {
        'password': 'somepass',
    },
    format='json'
)
assert api_response.status_code == 400

# Should fail become of missing username and password
api_response = client.post(
    '/user/register-user',
    format='json'
)
assert api_response.status_code == 400

# Should fail because user has been created above
api_response = client.post(
    '/user/register-user',
    {
        'username': 'someuser@domain.com',
        'password': 'somepass'
    },
    format='json'
)
assert api_response.status_code == 400

users_in_db = User.objects.all().count()
assert users_in_db == 1
In the case of certain tests such as when an API request is submitted without a password field, an error is returned that this field is required. However, the error message merely is “This field is required” as the error returned is from the UserSerializer validator for the password field. To make it clear that the field is missing, the error response returned in the view has to be changed to:
error_list = [
    '{error_field}-{error_text}'.format(
        error_field=e,
        error_text=user.errors[e][0].title()
    ) for e in user.errors
]
This formatting of the error string will make it clear with the field before the error message. Something that is interesting in the errors in the API responses is the error when the test attempts to create a duplicate user. The error is generated from the User model that is in-built with Django. However, examining the error message gives “username-A User With That Username Already Exists.” This shows that the error is generated by the serializer rather than the model. How does the error from the model get raised to the level of the Serializer that is created from the model? This is something that is enforced at the level of the database as the username is a unique field.

The next change that needs to be made is that a confirm_password field needs to be added when registering a user.

No comments:

Post a Comment