Reputation: 587
I have a function process_payment
that I want to do exception testing for:
def process_payment(event, context):
try:
payDictionary= json.loads(event["body"])
if "payment_method_id" in payDictionary:
intent = stripe.PaymentIntent.create(
payment_method = payDictionary['payment_method_id'],
amount = 1000,
currency = 'usd',
payment_method_types=["card"]
)
print(intent.status)
if intent.status == 'succeeded':
return {
"statusCode": 200,
"body": json.dumps({
'message': 'Payment succeeded',
}),
}
except Exception as e:
return {
"body": json.dumps({
"message": 'Payment failed. '+ str(e),
}),
}
I want to do exception testing for the above function so I wrote the following piece of code using the unittests framework for testing:
import unittest
from unittest import mock
from unittest.mock import patch, Mock
import json
import stripe
from stripe.util import convert_to_stripe_object
from . import app
def test_process_payment_exception(self):
event = {
'httpMethod': 'POST',
'body': '{"payment_method_id":"pm_1HGTb2GPqNNATumTCzrTXZ9e"}'
}
response = {}
with mock.patch('stripe.PaymentIntent.create', side_effect= Exception) as mock_process_payment:
stripe_obj = convert_to_stripe_object(response)
mock_process_payment.return_value= stripe_obj
self.assertRaises(Exception, app.process_payment, event, "")
This test code produces the following response:
======================================================================
FAIL: test_process_payment_exception (hello_world.test_app.TestStudent)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\...\sam-app\hello_world\test_app.py", line 524, in test_process_payment_exception
app.process_payment(event, "")
AssertionError: Exception not raised
----------------------------------------------------------------------
Ran 2 tests in 0.024s
FAILED (failures=1)
I am struggling to figure out how to do exception testing for this function without making additional changes to my original code.
EDIT:
I changed my code to look like this:
def test_process_payment_exception(self):
event = {
'httpMethod': 'POST',
'body': '{"payment_method_id":"pm_1HGTb2GPqNNATumTCzrTXZ9e"}'
}
def mock_method():
raise Exception("someMessage")
with mock.patch('stripe.PaymentIntent.create') as mock_process_payment:
stripe_obj = convert_to_stripe_object(response)
mock_process_payment.side_effect= mock_method
ret= app.process_payment(event, "")
print(ret)
getmessage= json.loads(ret['body'])
getmessageFinal= getmessage["message"]
self.assertEqual("someMessage", getmessageFinal)
This then produced the following response:
Traceback (most recent call last):
File "C:\Users\...\sam-app\hello_world\test_app.py", line 536, in test_process_payment_exception
....
......
+ Payment failed. mock_method() got an unexpected keyword argument 'payment_method'
-------------------- >> begin captured stdout << ---------------------
Payment failed. mock_method() got an unexpected keyword argument 'payment_method'
--------------------- >> end captured stdout << ----------------------
----------------------------------------------------------------------
Ran 2 tests in 0.024s
FAILED (failures=1)
I don't understand why I am seeing mock_method() got an unexpected keyword argument 'payment_method'
.
Upvotes: 0
Views: 301
Reputation: 16835
Your second variant is almost correct. The problem is that the side effect is called with the same arguments as the original method (create
), e.g. it is called like:
mock_method(payment_method, amount, currency, ...)
(all of these being keyword arguments). As mock_method
had no keyword
arguments defined, you got that error message (which mentions only the first missing argument).
From the documentation (relevent part emphasised):
side_effect: A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT, the return value of this function is used as the return value.
Therefore you have to allow for these arguments. If you are not interested in the actual arguments, you can just the generic notation *args, **kwargs
, where args
denotes all positional arguments (as a list) and kwargs
all kwyword arguments (as a dict).
In your case this is sufficient:
def test_process_payment_exception(self):
event = {
'httpMethod': 'POST',
'body': '{"payment_method_id":"pm_1HGTb2GPqNNATumTCzrTXZ9e"}'
}
def mock_method(*args, **kwargs):
raise Exception("The provided PaymentMethod ...")
with mock.patch('stripe.PaymentIntent.create') as mock_process_payment:
stripe_obj = convert_to_stripe_object(response)
...
Side note: "Payment failed:" is already prefixed by the calling code, so you shouldn't put it into the exception.
That being said, in your case it is easier to directly assign the exception to side_effect
:
with mock.patch('stripe.PaymentIntent.create') as mock_process_payment:
mock_process_payment = Exception("someMessage")
stripe_obj = convert_to_stripe_object(response)
You can directly assign an exception (not an exception class) to side_effect
, additionally to assigning a function (as shown above) or a list of values.
Upvotes: 1