Asserting that __init__ was called with right argu

2020-07-02 10:29发布

问题:

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)

回答1:

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()


回答2:

The problem is that Installer(...) doesn't call Installer.__init__ directly; rather, because Installer is an instance of type, and type.__call__ is defined, you get Installer() being equivalent to

type.__call__(Installer, ...)

which results in a call to Installer.__new__(Installer, ...), whose return value x is passed to Installer.__init__ along with the original arguments.

All of which is to say that since the mock object you bind to Installer isn't an instance of type, none of the preceding applies. Installer(...) is simply a call to a mock object, so you check that that directly:

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