1

In search of a fast stripe mock that didn't require an extra server to be running and produced realistic enough responses to use, I built the following class which works, but which is missing some functionality of the MagicMock, things like assertCalledWith, assertCalledOnce

I'm using this Mock class like so:

@mock.patch('stripe.Subscription', new_callable=StripeMockSubscription)
def test_something(self, _): #because the mock doesn't behaving like a MagicMock

But I would prefer to use it like:

@mock.patch('stripe.Subscription', new_callable=StripeMockSubscription)
def test_something(self, subscription_mock: mock.MagicMock):

but for that to work I need to figure out how to give the mock class MagicMock powers to track calls, parameters, etc... so that I get the side effects AND I get the MagicMock behavior.

It is possible to just mock out each method but then you end up with a stack of mocks on the top of the test that borders on unreadable. (Yes, I know you can put them on the test class, but you still have to enumerate all the arguments and if you don't need a mock on a specific test then you have unused parameters. (thanks for nothing pylint))

TLDR;

This is one of several classes for mocking out a portion of the stripe API, How do I turn this into something that also has the behavior of MagicMock on each of the methods? ie subscription_mock.retrieve.assertCalledOnce()

class StripeMockSubscription(real_stripe.Subscription):
    """ Mimics stripe.Subscription.* sort of.  The @classmethod is from the actual stripe
      API library """

    @classmethod
    def retrieve(cls, id: str, api_key=None, **kwargs):
        subscription = Subscription.objects.for_stripe_test_fixtures().get(stripe_id=id)
        context = {
            'sub': subscription,
            'schedule': 'null',
        }

        schedule = Schedule.objects.filter(subscription=subscription).first()
        if schedule:
            context['schedule'] = mark_safe(f"\"{schedule.schedule['id']}\"")

        if subscription.paid_until:
            context.update({
                'start_date': subscription.paid_until-timedelta(days=subscription.price.period_in_days),
                'end_date': subscription.paid_until,
            })
        else:
            context.update({
                'start_date': timezone.now(),
                'end_date': timezone.now()+timedelta(days=subscription.price.period_in_days),
            })

        sub = load_template_fixture('subscription.json', context=context)
        sub['url'] = 'https://stripe.com/goto-here/'
        # self['updated'] = 0

        return real_stripe.Subscription.construct_from(values=sub, key='FakeAPIKey')

    @classmethod
    def modify(cls, sid, **params):
        """ TODO: This could be better, for right now we just fake it until we make it! """
        return StripeModifyResponse()(sid, **params)

    @classmethod
    def create(cls, sid, **params):
        """ TODO: This could be better, for right now we just fake it until we make it! """
        return StripeCreateResponse()(sid, **params)
boatcoder
  • 17,525
  • 18
  • 114
  • 178

0 Answers0