在Python中模拟Stripe方法以进行测试

14

所以我正在尝试在这个方法中模拟所有的stripe web hooks,以便我可以为它编写单元测试。我正在使用mock库来模拟stripe方法。以下是我正在尝试模拟的方法:

class AddCardView(APIView):
"""
* Add card for the customer
"""

permission_classes = (
    CustomerPermission,
)

def post(self, request, format=None):
    name = request.DATA.get('name', None)
    cvc = request.DATA.get('cvc', None)
    number = request.DATA.get('number', None)
    expiry = request.DATA.get('expiry', None)

    expiry_month, expiry_year = expiry.split("/")

    customer_obj = request.user.contact.business.customer

    customer = stripe.Customer.retrieve(customer_obj.stripe_id)

    try:
        card = customer.sources.create(
            source={
                "object": "card",
                "number": number,
                "exp_month": expiry_month,
                "exp_year": expiry_year,
                "cvc": cvc,
                "name": name
            }
        )
        # making it the default card
        customer.default_source = card.id
        customer.save()
    except CardError as ce:
        logger.error("Got CardError for customer_id={0}, CardError={1}".format(customer_obj.pk, ce.json_body))
        return Response({"success": False, "error": "Failed to add card"})
    else:
        customer_obj.card_last_4 = card.get('last4')
        customer_obj.card_kind = card.get('type', '')
        customer_obj.card_fingerprint = card.get('fingerprint')
        customer_obj.save()

    return Response({"success": True})

这是进行 单元测试 的方法:

@mock.patch('stripe.Customer.retrieve')
@mock.patch('stripe.Customer.create')
def test_add_card(self,create_mock,retrieve_mock):
    response = {
        'default_card': None,
        'cards': {
            "count": 0,
            "data": []
        }
    }

    # save_mock.return_value = response
    create_mock.return_value = response
    retrieve_mock.return_value = response

    self.api_client.client.login(username = self.username, password = self.password)
    res = self.api_client.post('/biz/api/auth/card/add')

    print res

现在stripe.Customer.retrieve已经被正确模拟了。但我无法模拟customer.sources.create。我真的卡在这里了。

3个回答

18

这是正确的做法:

@mock.patch('stripe.Customer.retrieve')
def test_add_card_failure(self, retrieve_mock):
    data = {
        'name': "shubham",
        'cvc': 123,
        'number': "4242424242424242",
        'expiry': "12/23",
    }
    e = CardError("Card Error", "", "")
    retrieve_mock.return_value.sources.create.return_value = e

    self.api_client.client.login(username=self.username, password=self.password)

    res = self.api_client.post('/biz/api/auth/card/add', data=data)

    self.assertEqual(self.deserialize(res)['success'], False)

谢谢Shubham。你能举个例子说明模拟是如何工作的吗? - TD1

5
尽管给出的答案是正确的,但使用vcrpy可以更轻松地解决该问题。这就是一旦给定的记录不存在时创建一个cassette(记录)。当它存在时,模拟是透明的,并且将重放该记录。很美妙。
拥有一个普通的金字塔应用程序,在使用py.test时,我的测试看起来像这样:
import vcr 
# here we have some FactoryBoy fixtures   
from tests.fixtures import PaymentServiceProviderFactory, SSOUserFactory

def test_post_transaction(sqla_session, test_app):
    # first we need a PSP and a User existent in the DB
    psp = PaymentServiceProviderFactory()  # type: PaymentServiceProvider
    user = SSOUserFactory()
    sqla_session.add(psp, user)
    sqla_session.flush()

    with vcr.use_cassette('tests/casettes/tests.checkout.services.transaction_test.test_post_transaction.yaml'):
        # with that PSP we create a new PSPTransaction ...
        res = test_app.post(url='/psps/%s/transaction' % psp.id,
                            params={
                                'token': '4711',
                                'amount': '12.44',
                                'currency': 'EUR',
                            })
        assert 201 == res.status_code
        assert 'id' in res.json_body

我强烈推荐大家使用VCRpy。这是一个超级棒的库,极大地帮助我们减少了测试时间,而且最重要的是非常容易集成到我们的Django测试中。 - Amir Hadi
我在Ruby中广泛使用了VCR,不知道Python中有一个端口。谢谢@pansen! - frankV
1
我无法想象为什么有人会长期推荐使用VCR。这是一种懒惰的做法,而且你在维护/重建卡带方面会遇到更多问题(更不用说在版本控制中存储所有响应中的额外内容了),与其这样做,不如采用类似httmock或等效工具来正确地外部化依赖项。 - JoeLinux

5

依我之见,以下方法比其他回答更好

import unittest
import stripe
import json
from unittest.mock import patch
<b>from stripe.http_client import RequestsClient # to mock the request session</b>

stripe.api_key = "foo"

<b>stripe.default_http_client = RequestsClient() # assigning the default HTTP client</b>

null = None
false = False
true = True
charge_resp = {
    "id": "ch_1FgmT3DotIke6IEFVkwh2N6Y",
    "object": "charge",
    "amount": 1000,
    "amount_captured": 1000,
    "amount_refunded": 0,
    "billing_details": {
        "address": {
            "city": "Los Angeles",
            "country": "USA",
        },
        "email": null,
        "name": "Jerin",
        "phone": null
    },
    "captured": true,
}


def get_customer_city_from_charge(stripe_charge_id):
    # this is our function and we are writing unit-test for this function
    charge_response = stripe.Charge.retrieve("foo-bar")
    return <b>charge_response.billing_details.address.city</b>


class TestStringMethods(unittest.TestCase):

    <b>@patch("stripe.default_http_client._session")</b>
    def test_get_customer_city_from_charge(self, mock_session):
        mock_response = mock_session.request.return_value
        mock_response.content.decode.return_value = json.dumps(charge_resp)
        mock_response.status_code = 200

        <b>city_name = get_customer_city_from_charge("some_id")
        self.assertEqual(city_name, "Los Angeles")</b>


if __name__ == '__main__':
    unittest.main()

这种方法的优点

  1. 您可以生成相应的类对象(在这里,charge_response变量是Charge--(源代码) 的一种类型)
  2. 您可以像使用真实Stripe SDK一样使用响应上的点(.)运算符
  3. 点运算符支持深层属性

1
这个解决方案对我很有效,谢谢!现在我遇到了模拟错误响应的问题,但这是最好的响应。 - Jovani Martinez
我不确定如何在多个 Stripe 调用中执行此操作,因为模拟似乎仅限于单个 stripe.Charge.retrieve("foo-bar") 调用的范围。 - cevaris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接