Introduction

There are many articles available online that talk about dependency injection (DI) in Python.
Most of them suggest complexe methods for something that can be finally very easy to do using the built-in Python mechanisms.

Working with dependency injection is a good thing since it makes your code more testable and decoupled.

To implement dependency injection we are going to use the super method. Super calls the next dependency in the Method Resolution Order.

Example

Let’s say I am developing an (kind of) autonomous surveillance camera composed of a RC car with a camera on top of it.

I have the following class to steer my car:

Create a file autonomous_surveillance_car.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Car(object):
"""
Class that can drive a RCC car
"""

def turn_on(self):
print("Starting car...")

def move_forward(self):
print("Going forward...")

def move_backward(self):
print("Going backward...")

def turn_right(self):
print("Turning right...")

def turn_left(self):
print("Turning left...")


class AutonomousCar(Car):
"""
Automates the car directions to spy the house
"""

def spy(self, times=10):
super().turn_on()
for i in range(times):
super().move_forward()
super().turn_right()
super().move_forward()
super().turn_right()
super().move_forward()
super().turn_right()
super().move_forward()
super().turn_right()
# sleep x minutes


if __name__ == '__main__':
auto_car = AutonomousCar()
auto_car.spy()

Now I want to write tests for my autonomous car class car but I don’t want that my car starts going forward. This can be dangerous. I want to use a Mock. But I don’t want to modify the AutonomousCar(Car) line and replace the Car dependency with something like MockCar when I want to execute my tests.

The solution is to use a dependency injection:

Create a file called test_autonomous_surveillance_car.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import unittest
from autonomous_surveillance_car import AutonomousCar, Car


class MockCar(Car):
"""
Simulates a read RC car by recording tasks
"""

def __init__(self):
self.tasks = []

def turn_on(self):
self.tasks.append("Turning on car")

def move_forward(self):
self.tasks.append("Going forward")

def move_backward(self):
self.tasks.append("Going backward")

def turn_right(self):
self.tasks.append("Turning right")

def turn_left(self):
self.tasks.append("Turning left")


class MockedAutonomousCar(AutonomousCar, MockCar):
"""
Inject a mock car into the car dependency
"""


class TestAutonomousCar(unittest.TestCase):

def test_spy(self):
mocked_auto_car = MockedAutonomousCar()
mocked_auto_car.spy()
expected = (["Turning on car"] +
["Going forward", "Turning right"] * 40)
self.assertEqual(mocked_auto_car.tasks, expected)


if __name__ == "__main__":
unittest.main()

Now execute the test:

1
python -i test_autonomous_surveillance_car.py

Result:

1
2
3
4
5
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

In the prompt type help(MockedAutonomousCar).

You will see the Method Resolution Order (the dependencies order used when super is called) of the MockedAutonomousCar Class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Help on class MockedAutonomousCar in module __main__:

class MockedAutonomousCar(autonomous_surveillance_car.AutonomousCar, MockCar)
| Inject a mock car into the car dependency
|
| Method resolution order:
| MockedAutonomousCar
| autonomous_surveillance_car.AutonomousCar
| MockCar
| autonomous_surveillance_car.Car
| builtins.object
|

...

Conclusion

We see that our MockedAutonomousCar class uses depends on autonomous_surveillance_car.AutonomousCar class that depends on MockCar class. It does not depends on the Car class and we did not change our codebase. Moreover the implementation was very easy and just took 1 line of code.