Test-object builders for Stripe API resources

November 12, 2020
mockito python stripe unit-testing

Working with the Stripe API is unlike most anything else in the world of software development. Stripe’s documentation is outstanding. Their API is intuitive and the language specific clients are easy to use. Each Stripe account comes with a test environment that makes development and integration testing effortless. The stripe command line tool can be used for simple API queries, setting up authentication and testing of web hooks. On top of all that, there is stripe-mock, a local service with a near- complete implementation of the API that can be used for testing and development without requiring a network connection to the Stripe Test environment.

The issue with stripe-mock–and with testing against (near) “real” versions of any API–is that it adds additional overhead to each test because you’ll likely have to use the API to define or tweak test data before it can be used. Once stripe-mock gets statefulness, that overhead will grow.

This is fine for end-to-end testing where having tests walk a pre-designed path of API interactions that follow real-world usage is a good thing to do, but for unit testing, this amount of overhead leads to heavy tests that are hard to read and difficult to write. Life teaches us that if something is hard, we’re less likely to do lots of it. That is bad mojo for good test coverage.

In past projects, I have seen several ways of dealing with this problem, from a test-library of pre-defined hardcoded fixture data, to full-on crazy Java-style builder factory monstrosities with class names that put the average German word length to shame.

I started looking at the internals of the Stripe python API client and found something very interesting. The JSON structure returned by the API is parsed into a Python dict and then passed to a utility method that inspects the contents and builds the appropriate Stripe object hierarchy.

This sparked an idea. If I could define a Python dict with the right elements and invoke that Stripe utility method, I’d be able to define and build Stripe objects on the fly. Sprinkle in a little bit of builder-pattern and I’d have a quick, easy and readable way to build all varieties of Stripe objects needed to test my code. That in combination with Mockito (a great mocking framework for Python) would make for a very functional and pleasant way of unit testing a Stripe integration built with Python. And that’s exactly what I did.

Let’s look at a quick example of this through an imaginary Stripe integration function that retrieves the amount of an upcoming invoice for a customer defined by an email address.

We’ll have to look up the customer in Stripe using the email address (bad practice, I know, but bear with me here) and then invoke the API endpoint that returns the upcoming invoice and grab its amount_due field. Note that Customer.list returns a collection of Customer objects, even if there’s only one element.

import stripe
def upcoming_amount_due(customer_email):
    customers = stripe.Customer.list(email=customer_email)
    customer = customers.data[0]
    upcoming_invoice = stripe.Invoice.upcoming(customer=customer.id)
    return upcoming_invoice.amount_due

Writing a test for this example would require setting up a fair amount of fixture data as well as several API calls to get the expected state, regardless of whether we would use Stripe’s test environment or stripe-mock.

You’d have to create a Customer, ensure a Product structure is set up, then create a Subscription with one Subscription Item to your product and depending on the type of Subscription, you may even have to report some usage in order to trigger an upcoming invoice amount. I won’t bore you with a code example of all that, but I hope you agree that that would be a hassle.

Here’s how that same unit test would be written using the Stripe object builders and Mockito:

import stripe
from mockito import when
from unittest import TestCase
from my_stripe_implementation import upcoming_amount_due
from my_stripe_builders import *
class StripeUnitTest(TestCase):
    def test_upcoming_amount_due(self):
        test_email = "test@example.com"
        expected_amount = 5000
        customer_builder = StripeCustomerBuilder()
        customer = customer_builder.build()
        customer_list = StripeListBuilder().add(
            customer_builder).build()
        when(stripe.Customer).list(
            email=test_email).thenReturn(customer_list)
        when(stripe.Invoice).upcoming(
            customer=customer.id).thenReturn(
                StripeInvoiceBuilder()
                .amount_due(expected_amount)
                .build())

        self.assertEqual(expected_amount, upcoming_amount_due(test_email))

Using a combination of pre-defined fixture data and overrides where needed, the object builders allow for quickly setting up test data. The code under test invokes the Stripe API client, but through the use of Mockito, it will receive the test objects.

Yes, for this test, we could have entirely mocked out the objects with Mockito, but when the code under tests uses more of the fields, or when it loops through list of API results, it quickly becomes easier to just build the “real thing”.

Now that you have an idea of how these builders are used, let’s take a look under the covers. At the heart of it all lies the base class, StripeObjectBuilder.

import random
import string
from time import time
import stripe
class StripeObjectBuilder:
    def _build_data(self):
        return self._data
    def __init__(self):
        self._data = None
        self._id_str = ''.join(random.choice(
            string.ascii_letters + string.digits) for _ in range(16))
        self._now_epoch_secs = int(time())
    def _id(self, prefix: str):
        return "{}_{}".format(prefix.replace("_", ""), self._id_str)
    def build(self):
        return stripe.util.convert_to_stripe_object(self._build_data())
    def deleted(self):
        self._data = dict(
            id=self._data["id"],
            object=self._data["object"],
            deleted=True
        )
        return self
    def __getattr__(self, field_name):
        def set_data_field(value):
            self._data[field_name] = value
            return self
        return set_data_field

The object structure will be held in the _data field. _build_data() finalizes building of the object structure (reason for this will become apparent when we look at the list object below) which is then passed to stripe.util.convert_to_stripe_object() by build().

Builder classes for specific Stripe objects will extend StripeObjectBuilder and define the object structure by setting the _data field in their constructor.

__getattr__ allows overwriting any field in the _data dict and because self is returned, these methods can be chained for concise object construction syntax, as illustrated with .amount_due(expected_amount) in the example above.

Many Stripe objects like Customer, Subscription, etcetera have a metadata map for arbitrary key/value storage. We can build convenient support for that with the following extension:

class StripeObjectWithMetadataBuilder(StripeObjectBuilder):
    def __init__(self):
        self._data = None
        super().__init__()
    def set_metadata(self, key: str, value):
        if "metadata" not in self._data:
            self._data["metadata"] = dict()
        self._data["metadata"][key] = value
        return self

Before we get to the Customer builder, we need to get one more bit of infrastructure out of the way; Lists. Enter the StripeListBuilder:

class StripeListBuilder(StripeObjectBuilder):
    def _build_data(self):
        self._data["data"] = [item._build_data() for item in self._list_items]
        return self._data
    def __init__(self):
        super().__init__()
        self._list_items = []
        self._data = dict(
            object="list",
            data=None,
            has_more=False,
            total_count=0,
            url="",)
    def add(self, builder: StripeObjectBuilder):
        self._list_items.append(builder)
        self._data["total_count"] += 1
        return self

Items can be added to the list builder by passing their builder to the add() method. Note again that self is returned for method chaining. _build_data() loops through the _list_items and builds the _data structure.

Using this, we can now look at the StripeCustomerBuilder:

class StripeCustomerBuilder(StripeObjectWithMetadataBuilder):
    def _build_data(self):
        self._data["sources"] = self._sources._build_data()
        self._data["subscriptions"] = self._subscriptions._build_data()
        self._data["tax_ids"] = self._tax_ids._build_data()
        return self._data
    def __init__(self):
        super().__init__()
        self._sources = StripeListBuilder()
        self._subscriptions = StripeListBuilder()
        self._tax_ids = StripeListBuilder()
        self._data = dict(
            id=self._id("cus"),
            object="customer",
            address=dict(
                line1="123 main st",
                line2="apt 4",
                city="San Francisco",
                country="US",
                postal_code="94102",
                state="CA"
            ),
            currency="usd",
            created=self._now_epoch_secs,
            default_source=None,
            description="unit test customer 123 ",
            delinquent=False,
            email="test_customer@company.com",
            invoice_prefix="MYINVPREFIX",
            name="Test Customer",
            phone="41512367890",
            preferred_locales=["en"],
            sources=dict(),
            subscriptions=dict(),
            tax_exempt="exempt",
            tax_ids=dict(),
        )
    def add_subscription_builder(
            self, subscription_builder: StripeSubscriptionBuilder):
        subscription_builder.customer(self._data["id"])
        self._subscriptions.add(subscription_builder)
        return self

The constructor defines a basic set of fixture data. Embedded objects, like subscriptions in the subscriptions list can be added using add_… methods (for brevity, those for sources and tax_ids are omitted).

The fixture _data structure is based on the JSON hierarchy in the Customer API documentation. Any extra fields can be added as needed, depending on your implementation.

Using the Customer example above, it’s easy to write builders for other Stripe objects. I built them as my integration required new objects and found myself re-using them over and over again.

To round things out, here is another example of a complicated set of connected Stripe objects, defined in a few lines using Stripe Object Builders:

customer = (
    StripeCustomerBuilder()
        .name("John Doe")
        .delinquent(True)
        .email("jdoe@company.com")
        .add_subscription_builder(
            StripeSubscriptionBuilder()
                .add_item_builder(StripeItemBuilder(
                    StripePlanBuilder()
                        .nickname("3dollarMontly")
                        .amount(300)))
                .add_item_builder(StripeItemBuilder(
                    StripePlanBuilder()
                        .nickname("6dollarMonthly")
                        .amount(600)))
        )
    ).build()

In 17 lines of code, we build a Customer called John Doe, who can be emailed at jdoe@company.com about his lack of payment for a Subscription to the 3 and 6 dollars a month plan.

Using object builders like these, I’ve written lots of self-contained, readable unit tests for my Stripe integration code. I’m not sure if the world needs it, but I have been toying with the idea of creating an open source library that takes the ideas outlined above, combined with Stripe’s OpenAPI fixture data, to provide an auto-generated full featured set of object builders for use in any Stripe integration.

As always, I hope this was useful to you. If it is, or if you have suggestions, be sure to drop me a line.

-@niels

Testing Python with Mockito

November 10, 2020
A significant percentage of writing software is invoking other bits of software that may or may not be in your control. That sounds straightforward, after all most of this code doesn’t even really “do” anything. But as with most things in life, mistakes are made and thus small bugs find their way into your code. Testing is the obvious answer to this problem. Now before you climb your high horse and start pouring out the bucket of arguments on how good coders don’t need to write tests–and definitely not for simple code–, how testing takes too much time, that your boss won’t let you- or whatever other bad excuse (copied-from-some-Quora-answer-you-may-or-may-not-have-found-on-Google) you can bring up; here is why I think unit testing is valuable:..

mockito python unit-testing