isinstance and Mocking
Question:
class HelloWorld(object):
def say_it(self):
return 'Hello I am Hello World'
def i_call_hello_world(hw_obj):
print 'here... check type: %s' %type(HelloWorld)
if isinstance(hw_obj, HelloWorld):
print hw_obj.say_it()
from mock import patch, MagicMock
import unittest
class TestInstance(unittest.TestCase):
@patch('__main__.HelloWorld', spec=HelloWorld)
def test_mock(self,MK):
print type(MK)
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print v
if __name__ == '__main__':
c = HelloWorld()
i_call_hello_world(c)
print isinstance(c, HelloWorld)
unittest.main()
Here is the traceback
here... check type: <type 'type'>
Hello I am Hello World
True
<class 'mock.MagicMock'>
here... check type: <class 'mock.MagicMock'>
E
======================================================================
ERROR: test_mock (__main__.TestInstance)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/mock.py", line 1224, in patched
return func(*args, **keywargs)
File "t.py", line 18, in test_mock
v = i_call_hello_world(MK)
File "t.py", line 7, in i_call_hello_world
if isinstance(hw_obj, HelloWorld):
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
----------------------------------------------------------------------
Ran 1 test in 0.002s
Q1. Why is this error thrown? They are <class type='MagicMock>
Q2. How do I pause the mocking so that the first line will pass if the error is fixed?
From the docs:
Normally the __class__
attribute of an object will return its type. For a mock object with a spec, __class__
returns the spec class instead. This allows mock objects to pass isinstance()
tests for the object they are replacing / masquerading as:
mock = Mock(spec=3)
isinstance(mock, int)
True
Answers:
Don’t use isinstance
, instead check for the existence of the say_it
method. If the method exists, call it:
if hasattr(hw_obj, 'say_it'):
print hw_obj.say_it()
This is a better design anyway: relying on type information is much more brittle.
IMHO this is a good question and saying “don’t use isinstance
, use duck typing instead” is a bad answer. Duck typing is great, but not a silver bullet. Sometimes isinstance
is necessary, even if it is not pythonic. For instance, if you work with some library or legacy code that isn’t pythonic you must play with isinstance
. It is just the real world and mock was designed to fit this kind of work.
In the code the big mistake is when you write:
@patch('__main__.HelloWorld', spec=HelloWorld)
def test_mock(self,MK):
From patch
documentation we read (emphasize is mine):
Inside the body of the function or with statement, the target is patched with a new object.
That means when you patch the HelloWorld
class object the reference to HelloWorld
will be replaced by a MagicMock
object for the context of the test_mock()
function.
Then, when i_call_hello_world()
is executed in if isinstance(hw_obj, HelloWorld):
HelloWorld
is a MagicMock()
object and not a class (as the error suggests).
That behavior is because as a side effect of patching a class reference the 2nd argument of isinstance(hw_obj, HelloWorld)
becomes an object (a MagicMock
instance). This is neither a class
or a type
. A simple experiment to understand this behavior is to modify i_call_hello_world()
as follows:
HelloWorld_cache = HelloWorld
def i_call_hello_world(hw_obj):
print 'here... check type: %s' %type(HelloWorld_cache)
if isinstance(hw_obj, HelloWorld_cache):
print hw_obj.say_it()
The error will disappear because the original reference to HelloWorld
class is saved in HelloWorld_cache
when you load the module. When the patch is applied it will change just HelloWorld
and not HelloWorld_cache
.
Unfortunately, the previous experiment doesn’t give us any way to play with cases like yours because you cannot change the library or legacy code to introduce a trick like this. Moreover, these are that kind of tricks that we would like to never see in our code.
The good news is that you can do something ,but you cannot just patch
the HelloWord
reference in the module where you have isinstace(o,HelloWord)
code to test. The best way depends on the real case that you must solve. In your example you can just create a Mock
to use as HelloWorld
object, use spec
argument to dress it as HelloWorld
instance and pass the isinstance
test. This is exactly one of the aims for which spec
is designed. Your test would be written like this:
def test_mock(self):
MK = MagicMock(spec=HelloWorld) #The hw_obj passed to i_call_hello_world
print type(MK)
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print v
And the output of just unittest part is
<class 'mock.MagicMock'>
here... check type: <type 'type'>
I am fake
None
I’ve been wrestling with this myself lately while writing some unit tests. One potential solution is to not actually try to mock out the entire HelloWorld class, but instead mock out the methods of the class that are called by the code you are testing. For example, something like this should work:
class HelloWorld(object):
def say_it(self):
return 'Hello I am Hello World'
def i_call_hello_world(hw_obj):
if isinstance(hw_obj, HelloWorld):
return hw_obj.say_it()
from mock import patch, MagicMock
import unittest
class TestInstance(unittest.TestCase):
@patch.object(HelloWorld, 'say_it')
def test_mock(self, mocked_say_it):
mocked_say_it.return_value = 'I am fake'
v = i_call_hello_world(HelloWorld())
self.assertEquals(v, 'I am fake')
Michele d’Amico provides the correct answer in my view and I strongly recommend reading it. But it took me a while a grok and, as I’m sure I’ll be coming back to this question in the future, I thought a minimal code example would help clarify the solution and provide a quick reference:
from mock import patch, mock
class Foo(object): pass
# Cache the Foo class so it will be available for isinstance assert.
FooCache = Foo
with patch('__main__.Foo', spec=Foo):
foo = Foo()
assert isinstance(foo, FooCache)
assert isinstance(foo, mock.mock.NonCallableMagicMock)
# This will cause error from question:
# TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
assert isinstance(foo, Foo)
You can do it by being inherited from the MagicMock
class and overriding the __subclasscheck__
method:
class BaseMagicMock(MagicMock):
def __subclasscheck__(self, subclass):
# I couldn't find another way to get the IDs
self_id = re.search("id='(.+?)'", self.__repr__()).group(1)
subclass_id = re.search("id='(.+?)'", subclass.__repr__()).group(1)
return self_id == subclass_id
# def __instancecheck__(self, instance) for `isinstance`
And then you can use this class with the @patch
decorator:
class FooBarTestCase(TestCase):
...
@patch('app.services.ClassB', new_callable=BaseMagicMock)
@patch('app.services.ClassA', new_callable=BaseMagicMock)
def test_mock_for_issubclass_support(self, ClassAMock, ClassBMock):
check_for_subclasses(ClassAMock)
That’s it!
Remarks:
You MUST mock all classes which are compared using issubclass
.
Example:
def check_for_subclasses(class_1):
if issubclass(class_1, ClassA): # it's mocked above using BaseMagicMock
print("This is Class A")
if issubclass(class_1, ClassB): # it's mocked above using BaseMagicMock
print("This is Class B")
if issubclass(class_1, ClassC): # it's not mocked with @patch
print("This is Class C")
issubclass(class_1, ClassC)
will cause an error
{TypeError}issubclass() arg 1 must be a class
because ClassC
contains a default __issubclass__
method. And then we should handle the test like this:
class FooBarTestCase(TestCase):
...
@patch('app.services.ClassC', new_callable=BaseMagicMock)
@patch('app.services.ClassB', new_callable=BaseMagicMock)
@patch('app.services.ClassA', new_callable=BaseMagicMock)
def test_mock_for_issubclass_support(self, ClassAMock, ClassBMock):
check_for_subclasses(ClassAMock)
I think it’s safe to use freezegun. All the necessary preparations for correctly mocking the datetime
module are made there. Also, the isinstance
check does not fail for me.
It works like this:
@freeze_time("2019-05-15")
def test_that_requires_frozen_time(): ...
Just patch the isinstance method with:
@patch('__main__.isinstance', return_value=True)
So you will get expected behavior and coverage, you can always assert that mock method was called, see test case sample below:
class HelloWorld(object):
def say_it(self):
return 'Hello I am Hello World'
def i_call_hello_world(hw_obj):
print('here... check type: %s' %type(HelloWorld))
if isinstance(hw_obj, HelloWorld):
print(hw_obj.say_it())
from unittest.mock import patch, MagicMock
import unittest
class TestInstance(unittest.TestCase):
@patch('__main__.isinstance', return_value=True)
def test_mock(self,MK):
print(type(MK))
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print(v)
self.assertTrue(MK.say_it.called)
@patch('__main__.isinstance', return_value=False)
def test_not_call(self, MK):
print(type(MK))
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print(v)
self.assertFalse(MK.say_it.called)
isinstance
is a built-in function, and it is not a good idea to patch built-in functions as it is explained in this answer. In order to make isinstance
return the value you want, and avoid this error:
TypeError: isinstance() arg 2 must be a type or tuple of types
You can patch isinstance
in the module under test. I also encourage you to use patch
as a context manager in a with
statement as follows:
from mock import patch
def test_your_test(self):
# your test set up
with patch('your_module.isinstance', return_value=True): # or False
# logic that uses isinstance
Using patch
as a context manager allows you to mock just in the specific function/method that you want to mock it instead of mocking it in the whole test.
I guess the possible solution can be the checking of the subclass of object.
issubclass(hw_obj.__class__, HelloWorld)
Example:
from unittest.mock import patch, MagicMock
import unittest
class HelloWorld(object):
def say_it(self):
return 'Hello I am Hello World'
def i_call_hello_world(hw_obj):
print('here... check type: %s' % type(HelloWorld))
if isinstance(hw_obj, HelloWorld) or issubclass(hw_obj.__class__, HelloWorld):
print(hw_obj.say_it())
class TestInstance(unittest.TestCase):
@patch('__main__.isinstance', return_value=True)
def test_mock(self, MK):
print(type(MK))
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print(v)
self.assertTrue(MK.say_it.called)
@patch('__main__.isinstance', return_value=False)
def test_not_call(self, MK):
print(type(MK))
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print(v)
self.assertFalse(MK.say_it.called)
if __name__ == '__main__':
unittest.main()
I came to the same problem.
According to this answer https://stackoverflow.com/a/49718531/11277611
You can’t mock the second argument of isinstance()
I don’t know the solution but I know a good workaround:
It’s good architecture point to hind all checks (like isintanse
) inside separate functions because it’s not directly related dependency, in most cases you will change the checks but preserve the behavior.
Put isinstance
check into separate function and check it separately.
def logic_checks() -> bool:
...
return isinstance(mock, MyMock)
def main_func()
if logic_checks() is not True:
...
...
def test_main_func()
logic_checks = Mock(return_value=True)
main_func()
logic_checks.assert_called_once_with()
...
def test_logic_checks()
... # Here separate checks with another patches, mocks, etc.
class HelloWorld(object):
def say_it(self):
return 'Hello I am Hello World'
def i_call_hello_world(hw_obj):
print 'here... check type: %s' %type(HelloWorld)
if isinstance(hw_obj, HelloWorld):
print hw_obj.say_it()
from mock import patch, MagicMock
import unittest
class TestInstance(unittest.TestCase):
@patch('__main__.HelloWorld', spec=HelloWorld)
def test_mock(self,MK):
print type(MK)
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print v
if __name__ == '__main__':
c = HelloWorld()
i_call_hello_world(c)
print isinstance(c, HelloWorld)
unittest.main()
Here is the traceback
here... check type: <type 'type'>
Hello I am Hello World
True
<class 'mock.MagicMock'>
here... check type: <class 'mock.MagicMock'>
E
======================================================================
ERROR: test_mock (__main__.TestInstance)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/mock.py", line 1224, in patched
return func(*args, **keywargs)
File "t.py", line 18, in test_mock
v = i_call_hello_world(MK)
File "t.py", line 7, in i_call_hello_world
if isinstance(hw_obj, HelloWorld):
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
----------------------------------------------------------------------
Ran 1 test in 0.002s
Q1. Why is this error thrown? They are <class type='MagicMock>
Q2. How do I pause the mocking so that the first line will pass if the error is fixed?
From the docs:
Normally the
__class__
attribute of an object will return its type. For a mock object with a spec,__class__
returns the spec class instead. This allows mock objects to passisinstance()
tests for the object they are replacing / masquerading as:
mock = Mock(spec=3)
isinstance(mock, int)
True
Don’t use isinstance
, instead check for the existence of the say_it
method. If the method exists, call it:
if hasattr(hw_obj, 'say_it'):
print hw_obj.say_it()
This is a better design anyway: relying on type information is much more brittle.
IMHO this is a good question and saying “don’t use isinstance
, use duck typing instead” is a bad answer. Duck typing is great, but not a silver bullet. Sometimes isinstance
is necessary, even if it is not pythonic. For instance, if you work with some library or legacy code that isn’t pythonic you must play with isinstance
. It is just the real world and mock was designed to fit this kind of work.
In the code the big mistake is when you write:
@patch('__main__.HelloWorld', spec=HelloWorld)
def test_mock(self,MK):
From patch
documentation we read (emphasize is mine):
Inside the body of the function or with statement, the target is patched with a new object.
That means when you patch the HelloWorld
class object the reference to HelloWorld
will be replaced by a MagicMock
object for the context of the test_mock()
function.
Then, when i_call_hello_world()
is executed in if isinstance(hw_obj, HelloWorld):
HelloWorld
is a MagicMock()
object and not a class (as the error suggests).
That behavior is because as a side effect of patching a class reference the 2nd argument of isinstance(hw_obj, HelloWorld)
becomes an object (a MagicMock
instance). This is neither a class
or a type
. A simple experiment to understand this behavior is to modify i_call_hello_world()
as follows:
HelloWorld_cache = HelloWorld
def i_call_hello_world(hw_obj):
print 'here... check type: %s' %type(HelloWorld_cache)
if isinstance(hw_obj, HelloWorld_cache):
print hw_obj.say_it()
The error will disappear because the original reference to HelloWorld
class is saved in HelloWorld_cache
when you load the module. When the patch is applied it will change just HelloWorld
and not HelloWorld_cache
.
Unfortunately, the previous experiment doesn’t give us any way to play with cases like yours because you cannot change the library or legacy code to introduce a trick like this. Moreover, these are that kind of tricks that we would like to never see in our code.
The good news is that you can do something ,but you cannot just patch
the HelloWord
reference in the module where you have isinstace(o,HelloWord)
code to test. The best way depends on the real case that you must solve. In your example you can just create a Mock
to use as HelloWorld
object, use spec
argument to dress it as HelloWorld
instance and pass the isinstance
test. This is exactly one of the aims for which spec
is designed. Your test would be written like this:
def test_mock(self):
MK = MagicMock(spec=HelloWorld) #The hw_obj passed to i_call_hello_world
print type(MK)
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print v
And the output of just unittest part is
<class 'mock.MagicMock'>
here... check type: <type 'type'>
I am fake
None
I’ve been wrestling with this myself lately while writing some unit tests. One potential solution is to not actually try to mock out the entire HelloWorld class, but instead mock out the methods of the class that are called by the code you are testing. For example, something like this should work:
class HelloWorld(object):
def say_it(self):
return 'Hello I am Hello World'
def i_call_hello_world(hw_obj):
if isinstance(hw_obj, HelloWorld):
return hw_obj.say_it()
from mock import patch, MagicMock
import unittest
class TestInstance(unittest.TestCase):
@patch.object(HelloWorld, 'say_it')
def test_mock(self, mocked_say_it):
mocked_say_it.return_value = 'I am fake'
v = i_call_hello_world(HelloWorld())
self.assertEquals(v, 'I am fake')
Michele d’Amico provides the correct answer in my view and I strongly recommend reading it. But it took me a while a grok and, as I’m sure I’ll be coming back to this question in the future, I thought a minimal code example would help clarify the solution and provide a quick reference:
from mock import patch, mock
class Foo(object): pass
# Cache the Foo class so it will be available for isinstance assert.
FooCache = Foo
with patch('__main__.Foo', spec=Foo):
foo = Foo()
assert isinstance(foo, FooCache)
assert isinstance(foo, mock.mock.NonCallableMagicMock)
# This will cause error from question:
# TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
assert isinstance(foo, Foo)
You can do it by being inherited from the MagicMock
class and overriding the __subclasscheck__
method:
class BaseMagicMock(MagicMock):
def __subclasscheck__(self, subclass):
# I couldn't find another way to get the IDs
self_id = re.search("id='(.+?)'", self.__repr__()).group(1)
subclass_id = re.search("id='(.+?)'", subclass.__repr__()).group(1)
return self_id == subclass_id
# def __instancecheck__(self, instance) for `isinstance`
And then you can use this class with the @patch
decorator:
class FooBarTestCase(TestCase):
...
@patch('app.services.ClassB', new_callable=BaseMagicMock)
@patch('app.services.ClassA', new_callable=BaseMagicMock)
def test_mock_for_issubclass_support(self, ClassAMock, ClassBMock):
check_for_subclasses(ClassAMock)
That’s it!
Remarks:
You MUST mock all classes which are compared using issubclass
.
Example:
def check_for_subclasses(class_1):
if issubclass(class_1, ClassA): # it's mocked above using BaseMagicMock
print("This is Class A")
if issubclass(class_1, ClassB): # it's mocked above using BaseMagicMock
print("This is Class B")
if issubclass(class_1, ClassC): # it's not mocked with @patch
print("This is Class C")
issubclass(class_1, ClassC)
will cause an error
{TypeError}issubclass() arg 1 must be a class
because ClassC
contains a default __issubclass__
method. And then we should handle the test like this:
class FooBarTestCase(TestCase):
...
@patch('app.services.ClassC', new_callable=BaseMagicMock)
@patch('app.services.ClassB', new_callable=BaseMagicMock)
@patch('app.services.ClassA', new_callable=BaseMagicMock)
def test_mock_for_issubclass_support(self, ClassAMock, ClassBMock):
check_for_subclasses(ClassAMock)
I think it’s safe to use freezegun. All the necessary preparations for correctly mocking the datetime
module are made there. Also, the isinstance
check does not fail for me.
It works like this:
@freeze_time("2019-05-15")
def test_that_requires_frozen_time(): ...
Just patch the isinstance method with:
@patch('__main__.isinstance', return_value=True)
So you will get expected behavior and coverage, you can always assert that mock method was called, see test case sample below:
class HelloWorld(object):
def say_it(self):
return 'Hello I am Hello World'
def i_call_hello_world(hw_obj):
print('here... check type: %s' %type(HelloWorld))
if isinstance(hw_obj, HelloWorld):
print(hw_obj.say_it())
from unittest.mock import patch, MagicMock
import unittest
class TestInstance(unittest.TestCase):
@patch('__main__.isinstance', return_value=True)
def test_mock(self,MK):
print(type(MK))
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print(v)
self.assertTrue(MK.say_it.called)
@patch('__main__.isinstance', return_value=False)
def test_not_call(self, MK):
print(type(MK))
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print(v)
self.assertFalse(MK.say_it.called)
isinstance
is a built-in function, and it is not a good idea to patch built-in functions as it is explained in this answer. In order to make isinstance
return the value you want, and avoid this error:
TypeError: isinstance() arg 2 must be a type or tuple of types
You can patch isinstance
in the module under test. I also encourage you to use patch
as a context manager in a with
statement as follows:
from mock import patch
def test_your_test(self):
# your test set up
with patch('your_module.isinstance', return_value=True): # or False
# logic that uses isinstance
Using patch
as a context manager allows you to mock just in the specific function/method that you want to mock it instead of mocking it in the whole test.
I guess the possible solution can be the checking of the subclass of object.
issubclass(hw_obj.__class__, HelloWorld)
Example:
from unittest.mock import patch, MagicMock
import unittest
class HelloWorld(object):
def say_it(self):
return 'Hello I am Hello World'
def i_call_hello_world(hw_obj):
print('here... check type: %s' % type(HelloWorld))
if isinstance(hw_obj, HelloWorld) or issubclass(hw_obj.__class__, HelloWorld):
print(hw_obj.say_it())
class TestInstance(unittest.TestCase):
@patch('__main__.isinstance', return_value=True)
def test_mock(self, MK):
print(type(MK))
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print(v)
self.assertTrue(MK.say_it.called)
@patch('__main__.isinstance', return_value=False)
def test_not_call(self, MK):
print(type(MK))
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print(v)
self.assertFalse(MK.say_it.called)
if __name__ == '__main__':
unittest.main()
I came to the same problem.
According to this answer https://stackoverflow.com/a/49718531/11277611
You can’t mock the second argument of isinstance()
I don’t know the solution but I know a good workaround:
It’s good architecture point to hind all checks (like isintanse
) inside separate functions because it’s not directly related dependency, in most cases you will change the checks but preserve the behavior.
Put isinstance
check into separate function and check it separately.
def logic_checks() -> bool:
...
return isinstance(mock, MyMock)
def main_func()
if logic_checks() is not True:
...
...
def test_main_func()
logic_checks = Mock(return_value=True)
main_func()
logic_checks.assert_called_once_with()
...
def test_logic_checks()
... # Here separate checks with another patches, mocks, etc.