问题
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)
来源:https://stackoverflow.com/questions/35711975/asserting-that-init-was-called-with-right-arguments