How to patch classmethod with autospec in unmocked class? - python

I want to assert that one classmethod in a Python class calls another classmethod with a certain set of arguments. I would like the mocked classmethod to be "spec-ed", so it detects if it is called with the wrong number of arguments.
When I patch the classmethod using patch.object(.., autospec=True, ..), the classmethod is replaced with a NonCallableMagicMock and raises an error when I try to call it.
from mock import patch
class A(object):
#classmethod
def api_meth(cls):
return cls._internal_classmethod(1, 2, 3)
#classmethod
def _internal_classmethod(cls, n, m, o):
return sum(n, m, o)
with patch.object(A, '_internal_classmethod') as p:
print(type(p).__name__)
with patch.object(A, '_internal_classmethod', autospec=True) as p:
print(type(p).__name__)
produces the output:
MagicMock
NonCallableMagicMock
How can I get a spec-ed mock for _internal_classmethod when the class it belongs to is not mocked?

There's an outstanding bug report (google code link and python bug tracker link) to fix this issue. Until the fix gets incorporated, you can try the following, which worked for me [On 2.7, though I think it would also work in 3.x].
def _patched_callable(obj):
"Monkeypatch to allow autospec'ed classmethods and staticmethods."
# See https://code.google.com/p/mock/issues/detail?id=241 and
# http://bugs.python.org/issue23078 for the relevant bugs this
# monkeypatch fixes
if isinstance(obj, type):
return True
if getattr(obj, '__call__', None) is not None:
return True
if (isinstance(obj, (staticmethod, classmethod))
and mock._callable(obj.__func__)):
return True
return False
_patched_callable._old_func = mock._callable
mock._callable = _patched_callable
After the monkeypatch, you should be able to use mock.patch normally and have static- and class-methods patched properly.

Use spec in place of autospec, and set it directly.
with patch.object(A, '_internal_classmethod', spec=A._internal_classmethod) as p:
print(type(p).__name__)
gives me
MagicMock
for output.

Related

Python: is it possible to wrap "#patch(path)" for re-use? (unittest)

As the doc "Where to patch" says, we need to patch where an object is looked up, (not where it's defined); so I understand that it's not possible to - let's say - create a reusable patch for a particular path
Imagine you have several modules importing an object you'd like to mock
# file_a.py
from foo.goo.hoo import settings
# file_b.py
from foo.goo.hoo import settings
# file_c.py
from foo.goo.hoo import settings
I was wondering if there is a way to create a decorator such as:
#mock_settings
def test_whatever(self, settings_mock):
...
instead of this solution:
#patch("some_module.file_a.settings")
def test_whatever(self, settings_mock):
...
#patch("some_module.file_b.settings")
def test_whatever(self, settings_mock):
...
#patch("some_module.file_c.settings")
def test_whatever(self, settings_mock):
...
As mentioned in the question, to patch an object you have to patch its reference in the module to be tested (in case it is imported using from ...import).
To have it patched in several modules, you can patch all of these modules with the same mock, and use that mock. If you know in advance which modules you want to patch, you can just do this. If you don't know them in advance, you have to try to patch the object in all loaded modules -- this may get a bit more complicated.
I will show an example using pytest and a pytest fixture, as this is more compact; you could wrap that in a decorator for usage in unittest, but that will not change the basics. Consider we have a class that needs to be mocked in several modules:
class_to_mock.py
class ClassToMock:
def foo(self, msg):
return msg
module1.py
from class_to_mock import ClassToMock
def do_something():
inst = ClassToMock()
return inst.foo("module1")
module2.py
from class_to_mock import ClassToMock
def do_something_else():
inst = ClassToMock()
return inst.foo("module2")
You can now write a fixture that mocks the class in all of these modules at once (here using pytest-mock for simplicity):
#pytest.fixture
def mocked_class(mocker):
mocked = Mock()
for module in ('module1', 'module2'):
mocker.patch(module + '.ClassToMock', mocked)
yield mocked
This can be used to test both modules:
def test_module1(mocked_class):
mocked_class.return_value.foo.return_value = 'mocked!'
assert module1.do_something() == 'mocked!'
def test_module2(mocked_class):
mocked_class.return_value.foo.return_value = 'mocked!'
assert module2.do_something_else() == 'mocked!'
If you want a generic version that mocks the class in all loaded modules, you can replace the fixture with something like this:
#pytest.fixture
def mocked_class(mocker):
mocked = Mock()
for name, module in list(sys.modules.items()):
if not inspect.ismodule(module):
continue
for cls_name, cls in module.__dict__.items():
try: # need that as inspect may raise for some modules
if inspect.isclass(cls) and cls_name == "ClassToMock":
mocker.patch(name + ".ClassToMock", mocked)
except Exception:
continue
yield mocked
This will work for this specific example - to generalize this, it has to consider more object types, the class shall be configurable, and there may be some more issues - opposed to the more simple version where you just enumerate the modules you want to patch, which will always work.
You could do something similar in unittest.setUp by putting the mock in an instance variable, though that is less elegant, because you are also responsible for stopping the mocking:
class ModulesTest(unittest.TestCase):
def setUp(self):
self.mocked_class = Mock()
self.mocks = []
for module in ('module1', 'module2'):
mocked = mock.patch(module + '.ClassToMock', self.mocked_class)
self.mocks.append(mocked)
mocked.start()
def tearDown(self):
for mocked in self.mocks:
mocked.stop()
def test_module1(self):
self.mocked_class.return_value.foo.return_value = 'mocked!'
assert module1.do_something() == 'mocked!'
And you can also wrap this in a decorator, to answer your original question at least partially:
def mocked_class_to_mock(f):
#wraps(f)
def _mocked_class_to_mock(*args, **kwargs):
mocked_class = Mock()
mocks = []
for module in ('module1', 'module2'):
mocked = mock.patch(module + '.ClassToMock', mocked_class)
mocks.append(mocked)
mocked.start()
kwargs['mocked_class'] = mocked_class # use a keyword arg for simplicity
f(*args, **kwargs)
for mocked in mocks:
mocked.stop()
return _mocked_class_to_mock
...
#mocked_class_to_mock
def test_module3(self, mocked_class):
mocked_class.return_value.foo.return_value = 'mocked!'
assert module3.do_something() == 'mocked!'
Of course, you can do the same with the more generic version, if needed.
Also note that I skipped the simpler case where the object is imported using import .... In this case, you have to patch the original module. In the generic fixture, you probably want to add that case always.

Python mock a method when specific argument

I have a python method like
import external_object
from external_lib1 import ExternalClass1
from external_lib2 import Hook
class MyClass(self):
def my_method(self):
ExternalClass.get('arg1') #should be mocked and return a specific value with this arg1
ExternalClass.get('arg2') #should be mocked and return a specific value with this arg2
def get_hook(self):
return Hook() # return a mock object with mocked method on it
def my_method(self):
object_1 = external_object.instance_type_1('args') # those are two different object instanciate from the same lib.
object_2 = external_object.instance_type_2('args')
object_1.method_1('arg') # should return what I want when object_1 mocked
object_2.method_2 ('arg') # should return what I want when object_2 mocked
In my test I would like to realise what I put in comments.
I could manage to do it, but every time it gets really messy.
I use to call flexmock for some stuff (by example ExternalClass.get('arg1') would be mock with a flexmock(ExternalClass).should_return('arg').with_args('arg') # etc...) but I'm tired of using different test libs to mock.
I would like to use only the mock library but I struggle to find a consistent way of doing it.
I like to use python's unittest lib. Concretely the unittest.mock which is a great lib to customize side effects and return value in unit tested functions.
They can be used as follows:
class Some(object):
"""
You want to test this class
external_lib is an external component we cannot test
"""
def __init__(self, external_lib):
self.lib = external_lib
def create_index(self, unique_index):
"""
Create an index.
"""
try:
self.lib.create(index=unique_index) # mock this
return True
except MyException as e:
self.logger.error(e.__dict__, color="red")
return False
class MockLib():
pass
class TestSome(unittest.TestCase):
def setUp(self):
self.lib = MockLib()
self.some = Some(self.lib)
def test_create_index(self):
# This will test the method returns True if everything went fine
self.some.create_index = MagicMock(return_value={})
self.assertTrue(self.some.create_index("test-index"))
def test_create_index_fail(self):
# This will test the exception is handled and return False
self.some.create_index = MagicMock(side_effect=MyException("error create"))
self.assertFalse(self.some.create_index("test-index"))
Put the TestSome() class file somewhere like your-codebase-path/tests and run:
python -m unittest -v
I hope it's useful.

How to patch built-in/extension types in python using mockito?

While using mockito for unit testing in python, I came across an issue that I cannot find a solution for. I am trying to patch the usage of io.BytesIO in a given class method. The following code shows a simplified version where the issue occurs:
from mockito import mock, patch, when
from io import BytesIO
class Foo:
def bar(self):
buffer = io.BytesIO()
# ...
return buffer.getvalue()
def test_foo():
bytesIO_mock = mock(strict=True)
when(bytesIO_mock).getvalue().thenReturn('data')
patch(BytesIO.__new__, lambda: bytesIO_mock)
result = Foo().bar()
assert result == 'data'
I am getting the following error when I execute the test:
/venv/lib/python3.6/site-packages/mockito/mockito.py:270: in patch
when2(fn, Ellipsis).thenAnswer(replacement)
/venv/lib/python3.6/site-packages/mockito/mockito.py:245: in when2
return invocation.StubbedInvocation(theMock, name)(*args, **kwargs)
/venv/lib/python3.6/site-packages/mockito/invocation.py:284: in __call__
self.mock.stub(self.method_name)
/venv/lib/python3.6/site-packages/mockito/mocking.py:117: in stub
self.replace_method(method_name, original_method)
/venv/lib/python3.6/site-packages/mockito/mocking.py:108: in replace_method
self.set_method(method_name, new_mocked_method)
self = <mockito.mocking.Mock object at 0x10d50cb38>, method_name = '__new__'
new_method = <function Mock.replace_method.<locals>.new_mocked_method at 0x10d753e18>
def set_method(self, method_name, new_method):
> setattr(self.mocked_obj, method_name, new_method)
E TypeError: can't set attributes of built-in/extension type '_io.BytesIO'
/venv/lib/python3.6/site-packages/mockito/mocking.py:74: TypeError
Is there any solution to this issue, or is it just the case it is not possible to mock certain objects in python?
Instead of using patch you can simply mock the response from io.BytesIO() as follows:
def test_foo():
bytesIO_mock = mock(strict=True)
when(bytesIO_mock).getvalue().thenReturn('data')
when(io).BytesIO().thenReturn(bytesIO_mock)
result = Foo().bar()
assert result == 'data'
As a rule of thumb, all objects in Python can be mocked as everything in Python is an object. If you can not mock it, it is probably due to a limitation in the testing library/framework being used.

Asserting that __init__ was called with right arguments

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

Mocking the super class calls on python

I am doing some unit testing and at some point I need to mock a super call to throw an error, for example:
#classmethod
def myfunc(cls, *args, **kwargs)
try:
super(MyClass, cls).my_function(args, kwargs)
except MyException as e:
#...
I am using the mocker library to mock my objects in general but I haven't found a way to mock this.
Using unittest.mock from the standard library I would do something like this.
In your class definition:
from somelib import ASuperClass
class MyClass(ASuperClass):
def my_cool_method(self):
return super().my_cool_method()
In the module where you are calling MyClass:
from unittest.mock import patch
from mymodule import MyClass
#patch("mypackage.mymodule.ASuperClass.my_cool_method")
def call_with_mock(mocked_super):
myinstance = MyClass()
myinstance.my_cool_method()
# do stuff with `mocked_super`
call_with_mock()
I found a way, sort of hacky but it works, I'll explain with my example, this is based on this response so thanks #kindall:
def my_test(self):
import __builtin__
from mocker import Mocker, KWARGS, ARGS
mymocker = mocker.mock()
mymocker.my_function(ARGS, KWARGS)
mocker.throw(MyException)
def mysuper(*args, **kwargs):
if args and issubclass(MyClass, args[0]):
return mymocker
return original_super(*args, **kwargs)
__builtin__.original_super = super
__builtin__.super = mysuper
with mocker:
MyClass.myfunc()
so essentially what I do is check if the super call is from the class I want to mock, else just do a normal super.
Hope this helps someone :)
In case anyone needs another way to solve this mock:
# some_package/some_module.py
class MyClass(SuperClass):
def some_function(self):
result_super_call = super().function()
# test_file.py
#patch('some_package.some_module.super')
def test_something(self, mock_super):
obj = MyClass()
mock_super().some_function.return_value = None
Using Python 3.6
#Markus is looking in the right place. So long as you're unit testing (i.e. there's only one call to super), you can mock __builtin__.super as in:
with mock.patch('__builtin__.super') as mock_super:
mock_super.side_effect = TypeError
with self.assertRaises(TypeError):
obj.call_with_super()
Python's own Mock class provides a spec argument that should help with that:
with mock.patch('...ParentClass.myfunc') as mocked_fn:
mocked_fn.side_effect = MyException() # Parent's method will raise
instance = mock.Mock(spec=MyClass) # Enables using super()
MyClass.myfunc(instance) # Will enter your `except` block
Well, then you need to mock the my_function method of the superclass of MyClass to blow up.

Categories