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)
MagicMock. MagicMock objects provide a simple mocking interface that allows you to set the return value or other behavior of the function or object creation call that you patched. This allows you to fully define the behavior of the call and avoid creating real objects, which can be onerous.
side_effect: A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT , the return value of this function is used as the return value.
mock provides a powerful mechanism for mocking objects, called patch() , which looks up an object in a given module and replaces that object with a Mock . Usually, you use patch() as a decorator or a context manager to provide a scope in which you will mock the target object.
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With