Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection in Unit Test with Python

I am learning python

I'm wondering if there is a mechanism to "inject" an object (a fake object in my case) into the class under test without explicitly adding it in the costructor/setter.

## source file
class MyBusinessClass():
    def __init__(self):
        self.__engine = RepperEngine()

    def doSomething(self):
        ## bla bla ...
        success

## test file
## fake I'd like to inkject
class MyBusinessClassFake():
   def __init__(self):
      pass

def myPrint(self) :
    print ("Hello from Mock !!!!")

class Test(unittest.TestCase):

    ## is there an automatic mechanism to inject MyBusinessClassFake 
    ## into MyBusinessClass without costructor/setter?
    def test_XXXXX_whenYYYYYY(self):

        unit = MyBusinessClass()
        unit.doSomething()
        self.assertTrue(.....)

in my test I'd like to "inject" the object "engine" without passing it in the costructor. I've tried few option (e.g.: @patch ...) without success.

like image 837
Kasper Avatar asked Mar 10 '23 19:03

Kasper


1 Answers

IOC is not needed in Python. Here's a Pythonic approach.

class MyBusinessClass(object):
    def __init__(self, engine=None):
        self._engine = engine or RepperEngine() 
        # Note: _engine doesn't exist until constructor is called.

    def doSomething(self):
        ## bla bla ...
        success

class Test(unittest.TestCase):

    def test_XXXXX_whenYYYYYY(self):
        mock_engine = mock.create_autospec(RepperEngine)
        unit = MyBusinessClass(mock_engine)
        unit.doSomething()
        self.assertTrue(.....)

You could also stub out the class to bypass the constuctor

class MyBusinessClassFake(MyBusinessClass):
   def __init__(self):  # we bypass super's init here
      self._engine = None

Then in your setup

def setUp(self):
  self.helper = MyBusinessClassFake()

Now in your test you can use a context manager.

def test_XXXXX_whenYYYYYY(self):
  with mock.patch.object(self.helper, '_engine', autospec=True) as mock_eng:
     ...

If you want to inject it with out using the constuctor then you can add it as a class attribute.

class MyBusinessClass():
    _engine = None
    def __init__(self):
        self._engine = RepperEngine() 

Now stub to bypass __init__:

class MyBusinessClassFake(MyBusinessClass):
   def __init__(self):
      pass

Now you can simply assign the value:

unit = MyBusinessClassFake()
unit._engine = mock.create_autospec(RepperEngine)
like image 196
Dan Avatar answered Mar 20 '23 19:03

Dan