ContextDecorator does not restore mocked objects upon __exit__

103
March 13, 2018, at 06:28 AM

On a project I am working on, we are using stripe but we need to mock the API calls in our tests.

I wrote a class inheriting from contextlib.ContextDecorator so I can use it as a decorator and a context manager. Here is the code:

class mock_stripe(ContextDecorator):
    """Mock Stripe API calls.
    This class will be either working as a function decorator or as a
    context manager that will mock calls to the Stripe API, so there won't
    be any HTTP Request nor possible flakyness if the Stripe test API is
    down.
    """
    def __init__(self, customer=None, source=None,
                 retrieved_charge=None):
        """Initialize the context with objects to return upon mocked calls.
        If none of the arguments are provided, they will receive default
        values.
        Args:
            customer: The stripe customer to return.
            source: The stripe source to return.
            charge: The stripe charge to return.
            retrieved_charge: The stripe retrieved charge to return.
        """
        self.customer = customer
        self.source = source
        self.retrieved_charge = retrieved_charge
        self.pending_charge = MagicMock()
        self.pending_charge.status = 'pending'
        self.charge_state = 0
        if self.customer is None:
            self._create_default_customer()
        if self.source is None:
            self._create_default_source()
        if self.retrieved_charge is None:
            self._create_default_retrieved_charge()
    def _create_default_customer(self):
        self.customer = MagicMock()
        self.customer.stripe_customer_token = "token"
        self.customer.get_full_name = lambda: "Georges Abitbol"
        self.customer.email = "georges.abitbol@example.com"
        self.customer.number = "0123456789"
        self.customer.customer_id = "1"
        self.customer.id = "1"
        self.customer.source.id = "2"
    def _create_default_source(self):
        self.source = MagicMock()
        self.source.id = "2"
        self.source.customer_id = "1"
        self.source.sepa_debit.mandate_reference = "3"
        self.source.sepa_debit.mandate_url = "http://foo.bar/baz"
    def _create_default_retrieved_charge(self):
        self.retrieved_charge = MagicMock()
        self.retrieved_charge.status = 'succeeded'
    def __enter__(self):
        self.exit_stack = ExitStack()
        customer_create = self.exit_stack.enter_context(
            patch('stripe.Customer.create'))
        customer_retrieve = self.exit_stack.enter_context(
            patch('stripe.Customer.retrieve'))
        sources_create = self.exit_stack.enter_context(
            patch('stripe.Source.create'))
        charge_create = self.exit_stack.enter_context(
            patch('stripe.Charge.create'))
        charge_retrieve = self.exit_stack.enter_context(
            patch('stripe.Charge.retrieve'))
        customer_create.return_value = self.customer
        customer_retrieve.return_value = self.customer
        sources_create.return_value = self.source
        charge_create.return_value = self.pending_charge
        charge_retrieve.return_value = self.retrieved_charge
        return self
    def __exit__(self, *exc):
        self.exit_stack.pop_all()
        return False

The functions I patch are mocked, but the code in the __exit__ method does not restore the patched functions, and they remain mocked during the other tests, which I don't want to happend.

What did I do wrong?

READ ALSO
Seaborn Visualization for sum of columns

Seaborn Visualization for sum of columns

I am interested in these columns from the whole data set

111
Comparing column in spreadsheet against column in SQL table

Comparing column in spreadsheet against column in SQL table

Python newbie hereI am trying to write a small Python script to automate some work I do regularly

105
How to use integers in subprocess.call

How to use integers in subprocess.call

I am writing a program that runs qiimeI need the program to recognize numbers that the user types on the command line, but I think that subprocess

73
How to speed up this Python function?

How to speed up this Python function?

I hope it is OK to ask questions of this type

153