Using Python Class Methods to Enable Dependency Injection


Liz Johnson

on •

Jan 1, 1970

Using Python Class Methods to Enable Dependency Injection

Dependency injection is a useful pattern for many reasons, but one of the biggest reasons I tend to use it is that it easily allows you to mock out dependencies for testing purposes. But when an object requires multiple levels of dependencies to be built the construction of that object can look messy. I’ve found Python class methods can help clean up the construction of that object.

I’ve spent most of my time in Java, a bit in Typescript and most recently have spent a decent amount of time writing python. While patterns of testing can remain essentially the same between different languages the tools for testing change a bit from language to language.

If you’re writing a true unit test you’ll want to mock out dependencies so that you are only testing the logic in the class/module under test. The first thing I found to do this in python was patching. I used the annotations provided by the “unittest” library to patch my dependencies like this:

This caused me a lot of pain because I skipped the “read the instructions” step that explains to mock the dependency “where it’s used” and not “where it came from” (see “Where to Patch”). Knowing that helped. But then I was introduced to a new pattern that allowed me to avoid the @patch annotation almost entirely.

I’ll explain it here, because I really think it’s just the coolest. Here I’ll show how we build our classes so that they are clean and easily tested. Another thing you’ll see here is the pattern of making classes callable. This pattern feels like it goes well with dependency injection. You can give the class everything it needs, and then invoke it to let it do its thing.

I grew up in a family of four kids and I’ll never forget the coordination that went into soccer picture day. Four kids, four soccer games, and more activities beyond that - usually all in one Saturday.

So let’s say that I need a function that can assign children’s activities to a designated caretaker based on care-taker availability. This magical function just needs to do three things:

  1. Collect activities

  2. Collect care-taker openings

  3. Assign activities to a care-taker

We would like a test that can mock out the dependencies for this function (collect_activities, collect_openings, and assign_activity). That means our test would look something like this:

If we want a class that can assign activities for us and gets our test to pass we could build something that looks like the class below. Notice that we inject all our dependencies so that the mocking we do above is easy (and our @patch annotation doesn’t have to come into play at all):

That’s pretty basic dependency injection. My favorite part is coming next though. Let’s say we want this to be called from behind an endpoint, /assign. Here is an example of what the endpoint would look like using flask as our application framework. That endpoint could look something like this:

That looks okay because our class doesn’t have that many dependencies, nor are the dependencies it has very complicated. But let’s say that assign_acitivity isn’t a simple function, but rather a class with its own dependencies:

That’s becoming a lot to look at. But we can clean that up and make it look really nice! We can write a class method in the MakeAssignments class that allows the class to know how to build itself. For our MakeAssignments class that would look something like this:

Now in our assignment() method we can have something like this:

Nice! So much cleaner. And our test doesn’t change! We can instantiate the class with the mocks via the constructor for the test and we can use the build method in the rest of our code.

-   Liz Johnson, Senior Software Engineer at Artium