0

I have a API:

import stripe

class StripeWebHook(APIView):
    permission_classes = (AllowAny,)
    authentication_classes = ()

    def post(self, request, *args, **kwargs):
        payload = request.body
        try:
            event = stripe.Event.construct_from(
                json.loads(payload), stripe.api_key
            )
        except ValueError as e:
            return Response(status=400)

How can I write a test using patch for testing the request to an external API (e.g. stripe.Event.create) without transferring that function call from my main function?

I managed to test it by rewriting the function as follows:

def get_api_result(payload):
  return stripe.Event.construct_from(
            json.loads(payload), stripe.api_key
        )

class StripeWebHook(APIView):
    def post(self):
      payload = request.body
      res = get_api_result(payload)
      # ...

and using mock:

import mock
@mock.patch('get_api_result')
def test_payment(self, mockEvent) -> None:
    #...
    mockEvent.return_value = obj

But I don't like this approach. It doesn't seem right that I have to add another function just to mock it out.

I tried this

import stripe

class StripeWebHookTestCase(APITestCase):    
    @mock.patch('donation.views.stripe.Event.construct_from')
    def test_stripe_web_hook(self, mockEvent) -> None:
        # logic
        mockEvent.return_value = object
        resp = self.client.post(reverse('stripe-web-hook'))

But I get error Expecting value: line 1 column 1 (char 0)

Traceback

  File "/usr/local/lib/python3.7/json/__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "/usr/local/lib/python3.7/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/local/lib/python3.7/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

structure of the project

project/
  project/
    donation/
      tests/
        test_view.py   (StripeWebHookTestCase)
      views.py         (StripeWebHook)
    settings/
    manage.py

1 Answers1

1

You should be able to just patch stripe.Event.create directly. Just make sure to patch it where it's used:

patch() works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.

The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.

For example, something like this should work:

@mock.patch('appname.views.stripe.Event.create')
def test_payment(self, mock_event_create) -> None:
    #...
    mock_event_create.return_value = obj

I encourage you to read the entire section that's quoted in part above.

Community
  • 1
  • 1
ChrisGPT was on strike
  • 127,765
  • 105
  • 273
  • 257
  • Thnx. But I get `ValueError` (`Expecting value: line 1 column 1 (char 0)`). Updated my question... –  Feb 09 '20 at 18:43
  • You've changed the code in your question. What code results in that `ValueError`? Is there a line of code referenced in the error message? – ChrisGPT was on strike Feb 09 '20 at 19:02
  • I get error in the line `json.loads(payload), stripe.api_key` . See `traceback` –  Feb 09 '20 at 19:16
  • Source code - https://github.com/stripe/stripe-python/blob/master/stripe/stripe_object.py#L163 –  Feb 09 '20 at 19:18
  • Is it really impossible to just take and replace the api call with my object? Why is it still trying to call `stripe.Event.construct_from`? –  Feb 09 '20 at 19:20
  • No, it isn't impossible to replace the API call with whatever you want. I haven't said anything of the kind. You still aren't showing us the code you're trying to use to mock the call. Did you try mocking `appname.views.stripe.Event.construct_from()`? Please show the code where you try to mock that function. – ChrisGPT was on strike Feb 09 '20 at 19:54
  • @pythoner, what file is `StripeWebhook` in? And how do you import `stripe`? – ChrisGPT was on strike Feb 09 '20 at 20:37
  • The key, as the documentation I linked to says, is to make sure you're overriding the right name. If you import your view in your test as `from ..views import *`, try patching `..views.stripe.Event.construct_from`. If you're importing your view differently you'll have to adjust your import. (The example currently in my answer assumes you're doing `import appname.views` or similar. Once we get the import name and appropriate `patch()` call sorted out I'll update my answer.) – ChrisGPT was on strike Feb 09 '20 at 22:38
  • `stripe` it's a python `package` from `pypi` –  Feb 10 '20 at 08:07
  • I don't import views to my test –  Feb 10 '20 at 08:08
  • The question isn't about what `stripe` _is_, but rather what you're calling it. "I don't import views to my test"—ah. Then the patching won't work. We need a name that refers to `stripe.Event.construct_from` _in the test file_. Try adding `from ..views import stripe` and then patching `stripe.Event.construct_from`. – ChrisGPT was on strike Feb 10 '20 at 13:34