Home > database >  setUp function for python unittest doesn't use mocks declared over the class
setUp function for python unittest doesn't use mocks declared over the class

Time:11-11

so I'm writing unit tests and I'm having trouble with the setUp function. From what I've seen, it's supposed to just execute code before your function and thus I could put anything that's repetitive in there. However, this function doesn't seem to be applying the mocks I've created as patch decorators over the entire class. This is a small piece of what I want it to look like:

@patch('geomet_data_registry.layer.base.get_today_and_now', new=mocked_get_date)  # noqa
@patch('geomet_data_registry.layer.base.load_plugin', new=mocked_load_plugin)
@patch('geomet_data_registry.layer.base.TILEINDEX_PROVIDER_DEF', new=mocked_tileindex)  # noqa
@patch('geomet_data_registry.layer.base.STORE_PROVIDER_DEF', new=mocked_store)
class TestInitBase(unittest.TestCase):

    def setUp(self):
        """ Code that executes before every function. """

        self.maxDiff = None
        self.today_date = \
            datetime.now(timezone.utc).isoformat().replace(' 00:00', 'Z')
        mocked_get_date.return_value = self.today_date
        self.base_layer = BaseLayer({'name': 'model_gem_global'})

    def test_Init(self):
        
        expected_values = {'items': [],...

        base_layer_attr = self.base_layer.__dict__

        self.assertDictEqual(expected_values, base_layer_attr, msg=None)

Here I mock the date received so it doesn't mess with my tests, I mock load_plugin, which when used returns a class instance of a certain plugin, I mock TileIndex which is an ES TileIndex and I mock the store, which is a redis store. If I use the code shown above, it doesn't work. When I instantiate the class BaseLayer inside setUp, none of my mocks are used and I get:

-  'receive_datetime': '2021-11-10T12:56:07.371067Z',
?                                              ^^^
   'receive_datetime': '2021-11-10T12:56:07.371131Z',
?                                              ^^^
-  'store': <MagicMock name='mock()' id='140158154534472'>,
-  'tileindex': <MagicMock name='mock()' id='140158154534472'>,
   'store': <BaseStore> Redis,
   'tileindex': <ElasticsearchTileIndex> http://localhost:9200,

However, before you tell me maybe my paths are wrong for the mocks or something like that, I can assure you that everything works fine, because the code works if I keep everything the same, except I repeat the class instantiation in every test function. Moreover, it will work if I keep this the same, but I name the setUp for example mySetUp and call it at the beginning of the function.

Everything works this way and I've already made all my tests without using the setUp at all because I remember thinking to myself "this thing is broken I'm not risking using this function in my tests".

Thanks!

CodePudding user response:

The problem is that the mock.patch decorator is applied to each test function, and during setUp the patching has not been done yet. To use the same mocks for all tests, you have to start/stop mocking in setUp/tearDown instead. This could look something like:

class TestInitBase(unittest.TestCase):

    def setUp(self):
        self.data_patcher = patch('geomet_data_registry.layer.base.get_today_and_now',
            new=mocked_get_date)
        self.data_patcher.start()
        self.plugin_patcher = patch('geomet_data_registry.layer.base.load_plugin',
            new=mocked_load_plugin)
        self.plugin_patcher.start()
        ...

        self.today_date = \
            datetime.now(timezone.utc).isoformat().replace(' 00:00', 'Z')
        mocked_get_date.return_value = self.today_date
        self.base_layer = BaseLayer({'name': 'model_gem_global'})

    def tearDown(self):
        self.data_patcher.stop()
        self.plugin_patcher.stop()
        ...

Admittedly this is not as nice as using the decorators. You can still use the decorators for the functions that don't have to already be patched in setUp, if there are any.

As a side note: with pytest this would be a bit less messy, as you can have the setup and teardown part in the same fixture.

  • Related