Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I mock a superclass's __init__ create an attribute containing a mock object for a unit test?

Tags:

python

mocking

I am attempting to write a unit test for a class's __init__:

def __init__(self, buildNum, configFile = "configfile.txt"):
        super(DevBuild, self).__init__(buildNum, configFile)

        if configFile == "configfile.txt":
            self.config.MakeDevBuild()

The config attribute is set by the super's __init__. I'm using mock, and I want the config attribute to be a mock object. However, I haven't been able to figure out how to actually make that happen. Here's the best I could come up with for the test:

def test_init(self):
        with patch('DevBuild.super', create=True) as mock_super:
            mock_MakeDevBuild = MagicMock()
            mock_super.return_value.config.MakeDevBuild = mock_MakeDevBuild

            # Test with manual configuration
            self.testBuild = DevBuild("42", "devconfigfile.txt")
            self.assertFalse(mock_MakeDevBuild.called)

            # Test with automated configuration
            self.testBuild = DevBuild("42")
            mock_MakeDevBuild.assert_called_once_with()

However, this doesn't work--I get an error:

Error
Traceback (most recent call last):
  File "/Users/khagler/Projects/BuildClass/BuildClass/test_devBuild.py", line 17, in test_init
    self.testBuild = DevBuild("42")
  File "/Users/khagler/Projects/BuildClass/BuildClass/DevBuild.py", line 39, in __init__
    self.config.MakeDevBuild()
AttributeError: 'DevBuild' object has no attribute 'config'

Clearly I'm not setting the config attribute correctly, but I have no idea where exactly I should be setting it. Or for that matter, if what I want to do is even possible. Can anyone tell me what I need to do to make this work?

like image 948
khagler Avatar asked Jan 17 '13 15:01

khagler


1 Answers

You can't mock __init__ by setting it directly - see _unsupported_magics in mock.py.

As for what you can do, you can mock __init__ by passing it to patch, like so:

mock_makeDevBuild = MagicMock()
def mock_init(self, buildNum, configFile):
    self.config = MagicMock()
    self.config.MakeDevBuild = mock_makeDevBuild

with patch('DevBuild.SuperDevBuild.__init__', new=mock_init):
    DevBuild("42")
    mock_makeDevBuild.assert_called_once_with()

where SuperDevBuild is a base class of DevBuild.

If you really want to mock super(), you can perhaps make a class and then bind __init__ to object manually, like

mock_makeDevBuild = MagicMock()
def get_mock_super(tp, obj):
    class mock_super(object):
        @staticmethod
        def __init__(buildNum, configFile):
            obj.config = MagicMock()
            obj.config.MakeDevBuild = mock_makeDevBuild
    return mock_super
with patch('DevBuild.super', create=True, new=get_mock_super):
    DevBuild("42")
    mock_makeDevBuild.assert_called_once_with()

which works, but is quite ugly..

like image 126
jkozera Avatar answered Sep 20 '22 07:09

jkozera