Asserting that __init__ was called with right arguments

Srikanth picture Srikanth · Mar 1, 2016 · Viewed 8.5k times · Source

I'm using python mocks to assert that a particular object was created with the right arguments. This is how my code looks:

class Installer:
    def __init__(foo, bar, version):
        # Init stuff
        pass
    def __enter__(self):
        return self

    def __exit__(self, type, value, tb):
        # cleanup
        pass

    def install(self):
        # Install stuff
        pass

class Deployer:
    def deploy(self):
        with Installer('foo', 'bar', 1) as installer:
            installer.install()

Now, I want to assert that installer was created with the right arguments. This is the code I have so far:

class DeployerTest(unittest.TestCase):
    @patch('Installer', autospec=True)
    def testInstaller(self, mock_installer):
        deployer = Deployer()
        deployer.deploy()

        # Can't do this :-(
        mock_installer.__init__.assert_called_once_with('foo', 'bar', 1)

This is the error I get:

  File "test_deployment.py", line .., in testInstaller
    mock_installer.__init__.assert_called_once_with('foo', 'bar', 1)
AttributeError: 'function' object has no attribute 'assert_called_once_with'

Here is the fixed code (Call it test.py). Thanks, all!

import unittest
from mock import patch

class Installer:
    def __init__(self, foo, bar, version):
        # Init stuff
        pass

    def __enter__(self):
        return self

    def __exit__(self, type, value, tb):
        # cleanup
        pass

    def install(self):
        # Install stuff
        pass

class Deployer:
    def deploy(self):
        with Installer('foo', 'bar', 1) as installer:
            installer.install()

class DeployerTest(unittest.TestCase):
    @patch('tests.test.Installer', autospec=True)
    def testInstaller(self, mock_installer):
        deployer = Deployer()
        deployer.deploy()

        # Can't do this :-(
        # mock_installer.__init__.assert_called_once_with('foo', 'bar', 1)

        # Try this instead
        mock_installer.assert_called_once_with('foo', 'bar', 1)

Answer

idjaw picture idjaw · Mar 1, 2016

So, the error message you are getting is actually because you are not checking your mock properly. What you have to understand here is that in your decorator you are ultimately saying that, the call to Installer will relturn a Mock object instead.

Therefore, for any call to Installer() with respect to where you are patching, the return value of that will call Mock() instead.

So, the assertion you actually want to check is simply at the mock_installer, and not the mock_installer.__init__.:

mock_installer.assert_called_once_with('foo', 'bar', 1)

Here is the modification made to your code:

class DeployerTest(unittest.TestCase):

    @patch('Installer', autospec=True)
    def testInstaller(self, mock_installer):
        deployer = Deployer()
        deployer.deploy()

        mock_installer.assert_called_once_with('foo', 'bar', 1)

A little extra information to provide some more explanation, if you were testing now if install was called within your context manager, you have to realize here that you actually have to check inside your __enter__, so a structure would be like this:

For clarity sake create a mock_obj in your test method and:

mock_obj = mock_installer.return_value

Now, within your context manager, you will need to look inside the call to __enter__(). In case you don't know why this is, read up on context managers.

So, with that in mind, you simply perform your check as:

mock_obj.__enter__().install.assert_called_once_with()