pythonpython-mock

Mock a method of a patched class not working


I need to test a function that uses a lot of legacy code that I can't touch now. I patched the legacy class (LegacyClass) with the @patch decorator on my test class. The legacy class has a method get_dict(args) that returns a dictionary and for my test purpose I need to mock the return of get_dict(args) with a static dictionary.

class_to_test.py


from my_module import LegacyClass

class ClassToTest():
   self.legacy=LegacyClass() #I can't touch this part noe
   
   def method_to_test(self):
        d = self.legacy.get_dict()
        if d['data']['value'] == 1:
            return True
        else:
            return None
               

test.py

@patch('my_module.LegacyClass')
class TestClass(unittest.TestCase):

    def test_case(self, legacy_mock):
        legacy_mock.get_dict = MagicMock(return_value={'data': {'value': 1}})
        
        ctt = ClassToTest()
        assert ctt.method_to_test()

        

In my function, I use the returned dict to check if a specific value is present otherwise I return a default value. The function returns the default value even if the expected value is 1 as in the above example. It seems that the mock of get_dict doesn't work or I do something wrong.


Solution

  • Updated


    The main problem is that you trying to mock a property instance after initialization. In your case, the object will always be initialized during import. So the flow looks like:
    import ClassToTest -> import LegacyClass -> LegacyClass.__init__ -> mock.patch(...). You need to mock class before initialization if you want to skip all side-effects

    Here is an example:

    $ tree
    # output:
    ├── src
    │   ├── __init__.py
    │   ├── class_to_test.py
    │   ├── my_module.py
    ├── test_example.py
    

    src/my_module.py:

    class LegacyClass:
        def __init__(self) -> None:
            raise Exception('check side-effects')
    
        def get_dict(self) -> dict:
            raise NotImplemented()
    

    src/class_to_test.py:

    from src.my_module import LegacyClass
    
    class ClassToTest:
        legacy = LegacyClass()
    
        def method_to_test(self):
            d = self.legacy.get_dict()
            return d['data']['value'] == 1
    

    test_example.py:

    from unittest import TestCase, mock
    
    
    class TestExample(TestCase):
        def test_get_dict(self):
            with mock.patch('src.my_module.LegacyClass'):
                from src.class_to_test import ClassToTest
                to_test = ClassToTest()
    
                self.assertTrue(isinstance(to_test.legacy, mock.Mock))
                to_test.legacy.get_dict = mock.Mock(return_value={'data': {'value': 1}})
    
                self.assertTrue(to_test.method_to_test())
    

    Let's check:

    pytest test_example.py
    ============================================================================================= test session starts =============================================================================================
    ...
    collected 1 item                                                                                                                                                                                              
    
    test_example.py .                                                                                                                                                                                       [100%]
    
    ============================================================================================== 1 passed in 0.03s ==============================================================================================