vijay shanker
vijay shanker

Reputation: 2663

Mocking __init__ of a class imported in file

I have a file copy_x_to_y.py which goes like this:

from abcd import F

def function_to_be_tested():
     F()

in abcd.py file, i have something like this:

from xyz import XY

class F():
   def __init__(self, arg1):
       self.xy = XY(arg1)

I want to mock init of XY in my test case. I have tried mocking F's init with:

def mock_func(*args, **kwargs):
    pass

@patch('path/to/copy_x_to_y.F.__init__', mock_func)
def test():
    assert function_to_be_tested() is None

but it always happens to call XY's init, resulting in error as its initialization calls to connect with S3 with arg1. How to test this kind of structure?

Upvotes: 2

Views: 4904

Answers (1)

Robin Lundberg
Robin Lundberg

Reputation: 91

What is the reason for wanting to mock __init__ of XY? Do you want it to return a specific object of XY, do you want to check if XY.__init__ was called with specific arguments or something else?

A possible solution to your problem would be to mock the entire class, but have it return a "normal" object. Here's an example:

>>> from unittest.mock import patch
>>> class MyClass:
...   def __init__(self, val):
...     self._val = val
...   def foo(self):
...     print(self._val)
... 
>>> a = MyClass(1)
>>> a.foo()
1
>>> patcher = patch('__main__.MyClass', return_value=a)
>>> mock_class = patcher.start()
>>> b = MyClass(2)  # This will return a.
>>> b.foo()
1
>>> mock_class.call_args_list
[call(2)]
>>> patcher.stop()

Which in your case would be:

from xyz import XY
from path/to/copy_x_to_y import function_to_be_tested
def test():
  arg1 = ...
  a = XY(arg1)  # Has to be called before the patch to get a "normal" object.
  with patch('xyz.XY', return_value=a) as mock_xy:
    # Run funcion to be tested here and check results.
    function_to_be_tested()
    assert ...

Some side notes:

  1. It is possible to mock __init__ directly though, if that's really what you need to do.
>>> def my_init(self, *args, **kwargs):
...   self._val = 1
>>> patcher = patch.object(MyClass, '__init__', my_init)
>>> mock_init = patcher.start()
>>> a = MyClass(2)
>>> a.foo()
1
  1. If you use the patch decorator, you have to supply the decorated function with one extra argument that is the mock of the class or object. https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch
@patch('path/to/SomeClass', ...)
def test(mock_class):
...
  1. Also patch is typically (exclusively?) used to patch a class while patch.object is used to patch a member inside a class or module. https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch.object

Upvotes: 6

Related Questions