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?
Dependency injection is a way to scale the mocking approach. If a lot of use cases are relying on the interaction you'd like to mock, then it makes sense to invest in dependency injection. Systems that lend themselves easily to dependency injection: An authentication/authorization service.
Dependency injection is a technique built on the top of the Inversion of Control. The main idea is to separate the construction and usage of objects. As a software engineer, your objective is to build software so that it is modular and easy to extend.
If you want to mock an object for the duration of your entire test function, you can use patch() as a function decorator. These functions are now in their own file, separate from their tests. Next, you'll re-create your tests in a file called tests.py .
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...
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):
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With