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)