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
I think it's safe to use freezegun. All the necessary preparations for correctly mocking the
datetime
module are made there. Also, theisinstance
check does not fail for me.It works like this:
You can do it by being inherited from the
MagicMock
class and overriding the__subclasscheck__
method:And then you can use this class with the
@patch
decorator:That's it!
Remarks:
You MUST mock all classes which are compared using
issubclass
.Example:
issubclass(class_1, ClassC)
will cause an error{TypeError}issubclass() arg 1 must be a class
becauseClassC
contains a default__issubclass__
method. And then we should handle the test like this:Don't use
isinstance
, instead check for the existence of thesay_it
method. If the method exists, call it:This is a better design anyway: relying on type information is much more brittle.
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:
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. Sometimesisinstance
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 withisinstance
. 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:
From
patch
documentation we read (emphasize is mine):That means when you patch the
HelloWorld
class object the reference toHelloWorld
will be replaced by aMagicMock
object for the context of thetest_mock()
function.Then, when
i_call_hello_world()
is executed inif isinstance(hw_obj, HelloWorld):
HelloWorld
is aMagicMock()
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 (aMagicMock
instance). This is neither aclass
or atype
. A simple experiment to understand this behavior is to modifyi_call_hello_world()
as follows:The error will disappear because the original reference to
HelloWorld
class is saved inHelloWorld_cache
when you load the module. When the patch is applied it will change justHelloWorld
and notHelloWorld_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
theHelloWord
reference in the module where you haveisinstace(o,HelloWord)
code to test. The best way depends on the real case that you must solve. In your example you can just create aMock
to use asHelloWorld
object, usespec
argument to dress it asHelloWorld
instance and pass theisinstance
test. This is exactly one of the aims for whichspec
is designed. Your test would be written like this:And the output of just unittest part is
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: