pytestmagicmock

Mock inner class's attributes using MagicMock


Apologies for a length post. I have been trying to beat my head around reading about mock, MagicMock, and all the time getting confused. Hence, decided to write this post.

I know several questions, and pages have been written on this. But, still not able to wrap my head around this.

My Setup:

All the test code, and the 2 module files come under one "folder" mymodule

my_module_1.py file contains

class MyOuterClass(object):
   MyInnerClass(object):
      attribute1: str
      attribute2: str
      attribute3: str
      def get(self) -> MyInnerClass:
        ''' 
    pseudocode
         1. a call to AWS's service is made
         2. the output from call in step 1 is used to set attributes of this InnerClass
         3. return innerclass instance
        '''

I use the OuterClass in another file(my_module_2.py), to set some values and return a string as follows:

class MyModule2():
  def get_foo(self, some_boolean_predicate):
        if some_boolean_predicate:
            temp = my_module_1.OuterClass().get()
            statement = f'''
            WITH (
                BAR (
                    FIELD_1 = '{temp.attribute1}',
                    FIELD_2 = '{temp.attribute2}',
                    FIELD_3 = '{temp.attribute3}'
                )
            )
            '''
        else:
            statement = ''
        return statement

I want to write the unit tests for the file my_module_2.py file, and test the function get_foo

How I am writing the tests(or planning on) a test file by name test_my_module2.py

  1. I started with creating a pytest.fixture for the MyOuterClass's get function as follows since I will be reusing this piece of info again in my other tests
@pytest.fixture
def mock_get(mocker: MockerFixture) -> MagicMock:
    return mocker.patch.object(MyOuterClass, 'get')

Finally,

Then I proceeded to use this fixture in my test as follows:

from unittest import mock
from unittest.mock import MagicMock, Mock, patch, PropertyMock
import pytest
from pytest_mock import MockerFixture
from my_module.my_module_1 import myOuterClass

def test_should_get_from_inner_class(self, mock_get):
        # mock call to get are made
        output = mock_get.get
        #update the values for the InnerClass's attributes here
        output.attribute1.side_effect = 'attr1'
        output.attribute2.side_effect = 'attr2'
        output.attribute3.side_effect = 'attr3'
        mock_output_str = '''
            WITH (
                BAR (
                    FIELD_1 = 'attr1',
                    FIELD_2 = 'attr2',
                    FIELD_3 = 'attr3'
                )
            )
            '''
        module2Obj = MyModule2()
        response = module2Obj.get_foo(some_boolean_predicate=True)
        # the following assertion passes
        assert mock_get.get.called_once()
        # I would like match `response to that with mock_output_str instance above
        assert response == mock_output_str

But, the assertion as you might have guessed failed, and I know I am comparing completely different types, since I see errors such as

FAILED [100%]
            WITH (
                BAR (
                    FIELD1 = '<MagicMock name='get().attr1' id='4937943120'>',
                    FIELD3 = '<MagicMock name='get().attr2' id='4937962976'>',
                    FIELD3 = '<MagicMock name='get().attr3' id='4937982928'>'
                )
            )

Thank you for being patient with me till here, i know its a really lengthy post, but stuck on this for a few days, ended up creating a post here. How do i get to validate the mock's value with the mock_output_str?


Solution

  • yess! the hint was in the @gold_cy's answer. I was only calling my mock and never setting its values this is what my test case ended up looking

            mock_obj = OuterClass.InnerClass()
            mock_obj.attribute1='some-1'
            mock_obj.attribute2='some-2'
            mock_obj.attribute3='some-3'
            mock_get.return_value = mock_obj
    

    once my mock was setup properly, then the validation became easy! Thank you!