Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
kev26 opened this issue Jun 21, 2022 · 6 comments
Open

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

kev26 opened this issue Jun 21, 2022 · 6 comments

Comments

@kev26
Copy link

kev26 commented Jun 21, 2022

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?

@chapimenge3
Copy link

chapimenge3 commented Jul 5, 2022

@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.

@saaz181
Copy link

saaz181 commented Jul 16, 2022

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

@chapimenge3
Copy link

So when does the email is sent ? @saaz181

@saaz181
Copy link

saaz181 commented Jul 16, 2022

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,
        }

@chapimenge3
Copy link

chapimenge3 commented Jul 16, 2022

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

@skonik
Copy link

skonik commented Aug 4, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants