5

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.

1
  • For clarification, this is not dependency injection. You neither want the constructor, setter, nor service locator DI method. So this is just how to inject a mock without using DI. Commented Oct 18, 2022 at 20:26

4 Answers 4

8

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)
Sign up to request clarification or add additional context in comments.

1 Comment

This is just wrong. It introduces big trouble in future, consider someone add a new optional param which creates or deletes from real database, then all unittest will have direct access to it without explicit disable, which makes writing unittest a danger. Either pass in explicit or use a di framework, just do not default to prod service.
2

After years using Python without any DI autowiring framework and Java with Spring I've come to realize plain simple Python code often doesn't need frameworks for dependency injection without autowiring (autowiring is what Guice and Spring both do in Java), i.e., just doing something like this is enough:

def foo(dep = None):  # great for unit testing!
    self.dep = dep or Dep()  # callers can not care about this too
    ...

This is pure dependency injection (quite simple) but without magical frameworks for automatically injecting them for you (i.e., autowiring) and without Inversion of Control.

Unlike @Dan I disagree that Python doesn't need IoC: Inversion of Control is a simple concept that the framework takes away control of something, often to provide abstraction and take away boilerplate code. When you use template classes this is IoC. If IoC is good or bad it is totally up to how the framework implements it.

Said that, Dependency Injection is a simple concept that doesn't require IoC. Autowiring DI does.

As I dealt with bigger applications the simplistic approach wasn't cutting it anymore: there was too much boilerplate code and the key advantage of DI was missing: to change implementation of something once and have it reflected in all classes that depend on it. If many pieces of your application cares on how to initialize a certain dependency and you change this initialization or wants to change classes you would have to go piece by piece changing it. With an DI framework that would be way easier.

So 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):
    ...

Comments

0

Be explicit, use a setter. Why not?

class MyBusinessClass:
    def __init__(self):
        self._engine = RepperEngine()
from unittest.mock import create_autospec

def test_something():
  mock_engine = create_autospec(RepperEngine, instance=True)
  object_under_test = MyBusinessClass()
  object_under_test._engine = mock_engine

Comments

-1

Your question seems somewhat unclear, but there is nothing preventing you from using class inheritance to override the original methods. In that case the derivative class would just look like this:

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

3 Comments

"I'd like to "inject" the object "engine" without passing it in the costructor". I didn't get, what is not clear? other unit test framework in other languages (e.g.: java) have the possibility to inject object in other object without setter or costructor. that's all
It's unclear in that you ask simultaneously for a way to inject both 'MyBusinessClassFake' and 'engine' in a way that doesn't involve using monkey patching, inheritance, or passing any values, and that must also be automatic. If @patch is out of the question, are you sure that you cannot just do the following? unit = MyBusinessClass() unit.__engine = RepperEngine()
sorry there is a misunderstanding. Where did I say that I don't want to use @patch? patch is good to me ... I've just said that I was not able to get it working with @patch ....

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.