20
votes

Python is a relatively new language for me. Unit Testing and Dependency Injection are something that I've been doing for a little while now, so I'm familiar with it from a C# perspective.

Recently, I wrote this piece of Python code:

import requests  # my dependency: http://docs.python-requests.org/en/latest/

class someClass:
    def __init__(self):
        pass

    def __do(self, url, datagram):
        return requests.post(self, url, datagram)

And then I realized that I had just created a hard-coded dependency. Bleh.

I had considered changing my code to do "Constructor" Dependency Injection:

def __init__(self,requestLib=requests):
    self.__request = requestLib

def __do(self, url, datagram):
    return self.__request.post(self, url, datagram)

This now allows me to inject a fake/mock dependency for the sake of Unit Testing, but wasn't sure if this was considered Python-ic. So I'm appealing to the Python community for guidance.

What are some examples of Python-ic ways to do basic DI (mostly for the sake of writing Unit Tests that utilize Mocks/Fakes)?

ADDENDUM For anyone curious about the Mock answer, I decided to ask a separate question here: How does @mock.patch know which parameter to use for each mock object?

2
Note that __leading_double_underscore invokes name mangling, and should generally be avoided. Wouldn't it be easier to mock out requests for the module under test than inject it? - jonrsharpe
I read that the __leading double underscore was to mark a method private. Was I mistaken? If so, how should I mark something private? - Pretzel
Would it be easier to mock out requests for the module rather than inject it? I don't know. I'm not familiar with Python ways of doing things, which is why I'm asking. ;) - Pretzel
_leading_single_underscore is private-by-convention (see python.org/dev/peps/pep-0008) - nothing is ever truly private in Python, though, and even name-mangled attributes are accessible if you're determined. We're all consenting adults! - jonrsharpe
Ahh, single underscore. My bad. I'm still learning here. Thanks for your patience. Yeah, the Python way of doing things is quite different than what I'm used to. Yeah, I read about that "consenting adults" thing and laughed. - Pretzel

2 Answers

12
votes

Don't do that. Just import requests as normal and use them as normal. Passing libraries as arguments to your constructors is a fun thing to do, but not very pythonic and unnecessary for your purposes. To mock things in unit tests, use mock library. In python 3 it is built into the standard library

https://docs.python.org/3.4/library/unittest.mock.html

And in python 2 you need to install it separately

https://pypi.python.org/pypi/mock

Your test code would look something like this (using python 3 version)

from unittest import TestCase
from unittest.mock import patch

class MyTest(TestCase):
    @patch("mymodule.requests.post")
    def test_my_code(self, mock_post):
        # ... do my thing here...
6
votes

While injecting the requests module can be a bit too much, it is a very good practice to have some dependencies as injectable.

After years using Python without any DI autowiring framework and Java with Spring I've come to realize that plain simple Python code often doesn't need a framework for dependency injection with autowiring (autowiring is what Guice and Spring both do in Java), i.e., just doing something like this may be enough:

def foo(dep = None):  # great for unit testing!
    ...

This is pure dependency injection (quite simple) but without magical frameworks for automatically injecting them for you. The caller has to instantiate the dependency or you can do it like this:

def __init__(self, dep = None):
    self.dep = dep or Dep()

As you go for bigger applications this approach won't cut it though. For that I've come up with injectable a micro-framework that wouldn't feel non-pythonic and yet would provide first class dependency injection autowiring.

Under the motto Dependency Injection for Humans™ this is what it looks like:

# some_service.py
class SomeService:
    @autowired
    def __init__(
        self,
        database: Autowired(Database),
        message_brokers: Autowired(List[Broker]),
    ):
        pending = database.retrieve_pending_messages()
        for broker in message_brokers:
            broker.send_pending(pending)
# database.py
@injectable
class Database:
    ...
# message_broker.py
class MessageBroker(ABC):
    def send_pending(messages):
        ...
# kafka_producer.py
@injectable
class KafkaProducer(MessageBroker):
    ...
# sqs_producer.py
@injectable
class SQSProducer(MessageBroker):
    ...