djoser icon indicating copy to clipboard operation
djoser copied to clipboard

How to write unit test (APITestCase) include activation email !

Open kev26 opened this issue 2 years ago • 6 comments

My registeration follow : register --> get uid & token via email --> use ui & token for activation --> login--> accsess_token.

So if easily if I disable the feature SEND_ACTIVATION_EMAIL, but if I want to write a test with this feature, how to do it? And this is necessary for the test?

kev26 avatar Jun 21 '22 02:06 kev26

@kev26

My approach for this is i separate my settings file inside a folder like below image inside my setting file i put the setting file for production, development and test file. image

To explain the above file in the. Content of __init__.py image

i read the Environment file and decide which file to load.

The base.py contains the common setting configuration.

So here is the part where you can put your email activation class in the test.py like below.

image

soon we will write the class i mentioned in the above screenshot authentications.email.ActivationEmail

Now we are done with the setting part let's move to the email activation class where i put the class inside my authentications app and in email.py file but you can put it anywhere

So approach is to put all the email activations code in a dictionary at the run time and get the code from that dictionary.

FYI: this might be the dumbest approach but untill you get a workaround this might help.

let's continue

So in the authentications app email.py file create a class name called ActivationEmail

from django.contrib.auth.tokens import default_token_generator

# djoser imports
from templated_mail.mail import BaseEmailMessage
from djoser import utils
from djoser.conf import settings

EMAILS = {}

class ActivationEmail(BaseEmailMessage):
    """Email Activation Token Generator
    """
    template_name = "email/activation.html"

    def get_context_data(self):
        # ActivationEmail can be deleted
        context = super().get_context_data()
        user = context.get("user")
        context["uid"] = utils.encode_uid(user.pk)
        context["token"] = default_token_generator.make_token(user)
        context["url"] = settings.ACTIVATION_URL.format(**context)
        uid, token = context['uid'], context['token']
        EMAILS[user.email] = {'uid': uid, 'token': token}
        return context

So any in the testing file you can test the user creation like this

   def test_create_user(self):
        """
        Test for creating users using API.
        """
        url = reverse("user-list")
        response = self.client.post(
            url,
            self.user_info,
        )
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        user = User.objects.get(id=response.data['id'])
        self.assertEqual(user.email, self.user_info["email"])
        self.assertEqual(user.username, self.user_info["username"])
        # self.assertEqual(user.ssn, self.user_info["ssn"])
        self.assertTrue(user.password is not self.user_info["password"])
        self.assertTrue(user.is_deleted is not True)
        self.assertTrue(user.father_first_name is None)
        self.assertTrue(user.mother_first_name is None)
        self.assertTrue(user.password is not None)
        self.assertTrue(user.birth_date is not None)

    def test_get_token(self):
        """
        This test is used to test the login API. getting token and testing the token.
        """
        # Create a new user to login
        user_info = generate_user_info()
        new_user = self.client.post(
            reverse("user-list"),
            user_info,
        )
        self.assertEqual(new_user.status_code, status.HTTP_201_CREATED)

        # Activation of User
        from authentications.email import EMAILS

        activation_url = "http://127.0.0.1:8000/auth/users/activation/"
        activation_data = EMAILS[user_info["email"]]
        self.client.post(activation_url, activation_data)

        url = reverse("jwt-create")
        data = {
            "username": user_info["username"],
            "password": user_info["password"],
        }
        response = self.client.post(url, data)

        self.assertTrue(response.status_code, status.HTTP_200_OK)
        self.assertTrue(response.data["access"] is not None)

hope this helps but if anyone finds a better approach i would love to see it.

Enjoy.

chapimenge3 avatar Jul 05 '22 12:07 chapimenge3

Actually when testing to create account (user sign-up) it does NOT send activation email so it doesn't save any uid and token in EMAIL dict in email.py file

saaz181 avatar Jul 16 '22 10:07 saaz181

So when does the email is sent ? @saaz181

chapimenge3 avatar Jul 16 '22 10:07 chapimenge3

So when does the email is sent ? @saaz181

I set up my settings like you explained but it doesn't send any email it throws keyError on test_get_token function on: activation_data = EMAILS[self.user_info["email"]] so I printed out the EMAILS dict and it was an empty dict actually my user_info is like:

self.user_info = {
            "username": self.username,
            "phone": self.phone,
            "email": self.email,
            "password": self.password,
            "re_password": self.password,
        }

saaz181 avatar Jul 16 '22 10:07 saaz181

Perhaps you forget to change the environment variable.

Or you can do like this if you want

ENVIROMENT=test python manage.py test

Make you sure you run on the test enviroment becausethe setting wont be able to load the test setting config. you can check by adding print value on the settings/test.py if your print actually prints when you run the test it might be other problem.

@saaz181

chapimenge3 avatar Jul 16 '22 15:07 chapimenge3

I don't disable SEND_ACTIVATION_EMAIL. Just use regex to pull uid and token out of email and do POST request.

Here's my example using pytest:



@pytest.fixture
def email_activation_url():
    return reverse('user-activation')


@pytest.mark.django_db
def test_email_activation(
        api_client: APIClient,
        registration_url: str,
        email_activation_url: str,

):
    expected_user = dict(
        email="[email protected]",
        username="tester",
        password="superTester1sPass.",
    )

    response: Response = api_client.post(
        path=registration_url,
        data=expected_user,
    )
    assert response.status_code == status.HTTP_201_CREATED, response.data

    user = UserModel.objects.filter(email=expected_user['email']).first()
    assert user.is_active is False
    
    assert len(mail.outbox) == 1

    first_email = mail.outbox[0]
    
    # Check that we received activation email 
    activation_email_re = re.compile(
        r'.*Please go to the following page to activate account:.*',
        re.I + re.M,
    )
    activation_re_result = activation_email_re.search(first_email.body)
    assert activation_re_result is not None

    # regex template is based on string '#/activate/{uid}/{token}'. Check Djoser Settings
    activation_url_template = r'/activate/(?P<uid>.*)/(?P<token>.*)[\"\n>]'

    activation_url_re = re.search(
        activation_url_template,
        first_email.body,
        re.U + re.I + re.M,
    )
    assert activation_url_re is not None

    uid = activation_url_re.groupdict().get('uid')
    assert uid is not None

    token = activation_url_re.groupdict().get('token')
    assert token is not None

    response = api_client.post(
        path=email_activation_url,
        data={
            'uid': uid,
            'token': token
        }
    )

    assert response.status_code == status.HTTP_204_NO_CONTENT, response.data

    user.refresh_from_db()
    assert user.is_active is True

skonik avatar Aug 04 '22 07:08 skonik