twilio / twilio-python

A Python module for communicating with the Twilio API and generating TwiML.

Home Page:https://www.twilio.com/docs/libraries/python

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Django/ Pytest - Cannot mock patch verification / message service

Justinohallo opened this issue · comments

I am attempting to write a test for a service that uses the Twilio Verification service. I am basing my test off of the following article: https://www.twilio.com/blog/testing-twilio-applications-python-pytest

However, I cannot seem to properly target the client.verify.services.verifications.create method from the TwilioRest module as the request always gets sent to the Twilio server.

Has anyone had any success properly mocking this method ?

verification/services.py

from decouple import config
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException
from .config import INVALID_PARAMETER, TOO_MANY_REQUESTS


account_sid_production = config("TWILIO_ACCOUNT_SID_PRODUCTION")
auth_token_production = config("TWILIO_AUTH_TOKEN_PRODUCTION")
verify_service_sid = config("TWILIO_VERIFY_SERVICE_SID")
client = Client(account_sid_production, auth_token_production)


def verification_service_create(phone_number, channel="sms"):
    try:
        client.verify.services(verify_service_sid).verifications.create(
            to=phone_number, channel=channel
        )

        return (
            True,
            "Check your phone for verification code",
            "Verification Code successfully sent",
        )
    except TwilioRestException as error:
        if error.code == INVALID_PARAMETER:
            return (
                False,
                "Phone value is incorrectly formatted or is not a valid phone number.",
                "Use strict E.164 formatting, including the + sign,"
                "for phone numbers in the To parameter. Example: +15017122661.",
            )
        if error.code == TOO_MANY_REQUESTS:
            return (
                False,
                "You have sent too many requests to this service. Please try again later.",
                "You have sent too many requests to this service. Please try again later.",
            )
        return (
            False,
            "Internal Error",
            "Internal Error",
        )

verification/tests/services/test_verification_service_create.py

from unittest.mock import patch
from django.test import TestCase
from verification.services import verification_service_create


class TwilioVerificationServiceCreateTest(
    TestCase,
):


    @patch(
        "verification.services.client.verify.services.create", return_value=(True)
    )
    def test_verification_service_create_a(self, mock_verification_service_create):
        
        mock_verification_service_create.return_value = True
        is_success = verification_service_create("MOCK PHONE NUMBER")
        # self.assertIs(mock_utils.called, True)
        self.assertIs(is_success, True)
    @patch(
        "verification.services.client.verify.services.create", return_value=(True)
    )

Maybe since this is missing the verifications path part before create?

@childish-sambino

Thanks for looking into this.

I was wondering that as well, since the actual method is:

client.verify.services(sid).verifications.create

However, when I look into the chain of classes that produce this service from the Twilio library files, this is the path that I can find:

client.verify.services.create

I do not see the method "verifications" that exists between services and create. When looking at the services method, it returns a ServicesList class, with no mention of verifications.

When I attempt to mock the "verification.services.client.verify.services.verifications.create" method in my test, I get the following error.

E ModuleNotFoundError: No module named 'verification.services.client'; 'verification.services' is not a package

I can also produce this error when passing in an incorrect path on the original mock path:

verification.services.client.verify.services.createXYZ

When reviewing the path to verifications or verification_check, they are properties of the ServiceContext class. The verifications property returns the VerificationList class, which has a create method. However, there is no clear connection between the Client method and the ServiceContext class.

Was able to test locally with this:

    @patch(
        "verification.services.Client.verify"
    )
    def test_verification_service_create_a(self, mock_verify):
        mock_verify.services.return_value.verifications.create.return_value = True
        ....

This is great - I am able to prevent this function from hitting the Twilio servers.

However, were you able to properly use the mock side effect?

@patch(
        "verification.services.Client.verify"
    )
    def test_verification_service_create_a(self, mock_verify):
        mock_verify.services.return_value.verifications.create.return_value = True
        status = 500
        uri = '???????'
        msg = error_message
        mock_verify.side_effect = TwilioRestException(status, uri, msg=error_message)
        ....

I cannot seem to get this to work correctly using the path you provided above. I think it has something to do with the URI. Do you know where the URI for the verify service would be listed ?

The side_effect property needs to be on the create function to mimic what would happen when there's an API failure that results in an exception being raised. E.g.,

mock_verify.services.return_value.verifications.create.side_effect = TwilioRestException(status, uri, msg=error_message)

@childish-sambino Thank you for all of your help. This should be turned into an article on the Twilio Django site.

Hey @childish-sambino,

Thanks for all of your help on the previous issue but am now running into the same issue with the messages API.

I tried using the same path from above, but I can not catch the side effect or receive a True value when evaluating if the method was called.

@patch( "verification.services.Client.messages" ) def test_message_send(self, mock_send): mock_verify.services.return_value.messages.create.return_value = True ....

Do you know why these paths are so different than the ones described in this article ?

https://www.twilio.com/blog/testing-twilio-applications-python-pytest

Do you have any advice on how to read the core python files that make up this library so I can figure this out in the future without needing to bug you ?

Hi @Justinohallo, see here for better documentation on how to use the patch decorator and specify the target path.

Closing due to inactivity. Please re-open this issue or open a new GitHub issue if you still need help.