How to do basic dependency injection in Python (for mocking/testing purposes) How to do basic dependency injection in Python (for mocking/testing purposes) python python

How to do basic dependency injection in Python (for mocking/testing purposes)


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 TestCasefrom unittest.mock import patchclass MyTest(TestCase):    @patch("mymodule.requests.post")    def test_my_code(self, mock_post):        # ... do my thing here...


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.pyclass 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@injectableclass Database:    ...
# message_broker.pyclass MessageBroker(ABC):    def send_pending(messages):        ...
# kafka_producer.py@injectableclass KafkaProducer(MessageBroker):    ...
# sqs_producer.py@injectableclass SQSProducer(MessageBroker):    ...