Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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?

like image 685
Pretzel Avatar asked Oct 26 '15 15:10

Pretzel


People also ask

Is mocking a dependency injection?

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.

How do you use dependency injection in Python?

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.

How do you make a mock in Python?

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 .


2 Answers

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...
like image 66
Mad Wombat Avatar answered Oct 18 '22 07:10

Mad Wombat


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):
    ...
like image 41
Rodrigo Martins de Oliveira Avatar answered Oct 18 '22 07:10

Rodrigo Martins de Oliveira