1

I'm trying to mock a method in a parent class that is declared out of any class method. The problem es I can't figure out how to make the LoggerUtils class to be instantiated in the parent class. Puting it inside an __init__ is not an option due to the large size of the implementation and the cost of refactor.

  • Is there a way to mock the parent class prior to the import of the tested class?

  • At least mock the method before is loaded when importing the class.

  • Is there any approach to solve the problem of non-lazy methods loading on import?

I tried lazy loading TensorFlow LazyLoad Library methods but I just didnt get it to work; patching all methods with mock library, but the methods always load before I can mock anything. Below I have an example of the mocking tries, but LoggerUtils is always called.

Parent Class:

abstract_api.py

class AbstractApi:

    logger = LoggerUtils.get_logger('AbstractApi')
    def update(self):
        <code>


Class to test:

api_map_asset_type.py

from abstract_api import AbstractApi


class ApiMapAssetType(AbstractApi):
    def update(self):
        <code>

Test Class:

test_api_map_asset_type.py

from unittest import TestCase
from mock imort patch
from api_map_asset_type import ApiMapAssetType


class TestApiMapAssetType(TestCase):

    @patch('api_map_asset_type.AbstractApi.LoggerUtils')
    @patch('api_map_asset_type.AbstractApi')
    def setUp(self, mock2_abstract_loger, mock_3):
        self.asset_api = ApiMapAssetType()

    @patch('AbstractApi.update')
    def test_update(self, mock_parent_update):

        mock_orm = MagicMock()
        self.asset_api.update()
        mock_parent_update.assert_called_with()

EDITED

This is the only solution I found, since I am not able to mock parent classes or mock methods in class attributes before being imported, I decided to mock the whole test before importing, yet I think this is not an optimal or clean solution:

Test Class:

test_api_map_asset_type.py

from undetermined_project_library.LoggerUtils import LoggerUtils
with patch.object(LoggerUtils, 'get_logger') as mock_logger:

    from unittest import TestCase
    from mock imort patch
    from api_map_asset_type import ApiMapAssetType


    class TestApiMapAssetType(TestCase):

    def setUp(self):
            self.asset_api = ApiMapAssetType()

        @patch('AbstractApi.update')
        def test_update(self, mock_parent_update):

            mock_orm = MagicMock()
            self.asset_api.update()
            mock_parent_update.assert_called_with()

  • I don't really understand what you are trying to do, but the `patch` calls look wrong: you patch `api_map_asset_type.AbstractApi` and then `AbstractApi.LoggerUtils`. Make sure to [use the correct pointer](https://docs.python.org/3.8/library/unittest.mock.html?#id6) to the patched object. – MrBean Bremen Mar 16 '20 at 17:49
  • I was trying different combinations patching the classes, but any worked. The problem is, that as someone called de Logger class in the class attributes, this methods are called on import, as Python doesn't do lazy imports. I was trying to find a way to mock this methods before being imported. – Alfonso Jimenez Mar 16 '20 at 18:11
  • 1
    So in your test, you call `ApiMapAssetType()` in `setUp`, but you did not import it - so I guess you didn't show the complete code. Do you do a local import there? Please show a [reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – MrBean Bremen Mar 16 '20 at 18:25
  • Well, I placed the import, I had tried putting it before/after patch, or faking it with sys.modules load. I also tried LazyLoad functions. – Alfonso Jimenez Mar 17 '20 at 07:35
  • I still think the patch for `LoggerUtils` is incorrect - make sure to patch it the same way as you would access it - maybe `api_map_asset_type.AbstractApi.LoggerUtils`, or the respective module, where `AbstractApi` is located. – MrBean Bremen Mar 17 '20 at 07:42
  • I doesnt matter the way you call it, I tried every possibility. The problem is that even when you are refering a class with patch method, class attributes are instantiated. – Alfonso Jimenez Mar 17 '20 at 09:03
  • I just came with a quick fix, dont find anywhere how to mock parent claeses or prevent python from executing code on import. – Alfonso Jimenez Mar 17 '20 at 09:59

2 Answers2

1

Provided I understood your module layout, this should work:

class TestApiMapAssetType(TestCase):

    @patch('undetermined_project_library.LoggerUtils')
    def setUp(self, mock_abstract_logger):
        self.asset_api = ApiMapAssetType()

    @patch('api_map_asset_type.ApiMapAssetType.update')
    def test_update(self, mock_parent_update):
        self.asset_api.update()
        mock_parent_update.assert_called_with()

Note a couple of things:

  • you have to mock LoggerUtils from the library they are defined in; you have used AbstractApi.LoggerUtils, which is not correct, as LoggerUtils does not belong to AbstractApi
  • I removed the mock for AbstractApi - at least for this you don't need it
  • update is patched for the method that is actually called - using the base class method may make sense if you call the base implementation (don't know if you do)
  • I guessed some of your module layout - you may have to adapt it
MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
0

You can do this by using the sys module in python. You can register the imports with sys module and replace the implementation with Mock or MagicMock instances.

This is how it looks like in code -

import sys
from unittest.mock import MagicMock

sys.modules['numpy'] = MagicMock()
sys.modules['pandas'] = MagicMock()
sys.modules['torch'] = MagicMock()
sys.modules['torch.nn'] = MagicMock()
sys.modules['torch.nn.functional'] = MagicMock()
sys.modules['dgl'] = MagicMock()
sys.modules['dgl.ops'] = MagicMock()
sys.modules['dgl.function'] = MagicMock()

from path.to.module_under_test import ClassUnderTest

Assuming module_under_test.py starts like -

import numpy as np
import pandas as pd
import torch
import dgl
import torch.nn.functional as F
from dgl.ops import edge_softmax


class ClassUnderTest:
    # code
Rahul Nimbal
  • 525
  • 7
  • 11