isinstance and Mocking

pytest-mock
python mock property
python mock assert not called
python mock constructor
python mock attribute
mock vs magicmock
python mock variable in function
python mock multiple return values
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

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.

Allow a mock to pass an issubclass test or to be used as the second , This would be a pain for the mock code itself as it does type-checking in a few places (treating mocks specially). It can't use isinstance because of the __class__​  4 Solutions collect form web for “isinstance и Mocking” Не используйте isinstance , вместо этого проверяйте существование метода say_it . Если метод существует, назовите его:

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

unittest.mock — mock object library, This allows mocks to pass isinstance() tests. spec_set: A stricter variant of spec. If used, attempting to set or get an attribute on the mock that isn't on the object  isinstance and Mocking (4) Don't use isinstance, instead check for the existence of the say_it method. If the method exists, call it: if hasattr (hw_obj

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)

What the mock?, accessing one of its attributes; calling the object itself. from unittest import mock m = mock.Mock() assert isinstance(m.foo, mock. Mocking spirits make fun of your God-given identity and purpose in life to limit you! God says you are more than good enough, you more than a conqueror! (Romans 8:37) God called you worthy and you can do all things through Jesus Christ who strengthens you.

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)

[TIP] Issue with "mock" package and isinstance(), Somewhere else, my code gets this mock client and a function calls: >>>>> isinstance(client, my.module.path.Class): >>>>> This call fails  unittest.mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used. unittest.mock provides a core Mock class removing the need to create a host of stubs throughout your test suite. After performing an action, you can make assertions about which methods / attributes were used and arguments they were called with.

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

Mocking in Python ⋆ Mark McDonnell, In this post I am going to cover various aspects of mocking code, which will hopefully be a SimpleAsyncHTTPClient) # True isinstance(mock. Synonyms for mocking at Thesaurus.com with free online thesaurus, antonyms, and definitions. Find descriptive alternatives for mocking.

unittest.mock, mock.py # Test tools for mocking and patching. _is_instance_mock(obj): # can't use isinstance on Mock objects because they override __class__ # The base  Python Mock/MagicMock enables us to reproduce expensive objects in our tests by using built-in methods (__call__, __import__) and variables to “memorize” the status of attributes, and function calls. We can use them to mimic the resources by controlling how they were created, what their return value is.

mock - Add a spec to a mock object - Python code example, from mock import Mock. class C: def get_val(self): print 1 mock = Mock() mock.​mock_add_spec(C()) mock.get_val.side_effect = C().get_val print isinstance(​mock,  isinstance (object, classinfo) object Required. An object instance. classinfo Required. A class, type or a tuple containing classes, types or other tuples.

mock.py - infra/third_party/mock, def _is_instance_mock(obj):. # can't use isinstance on Mock objects because they override __class__. # The base class for all mocks is NonCallableMock. Maybe something along the lines of below that if there is a __code__ attribute then always check it's of CodeType. So that my test passes with MagicMock.__code__ detected. If I understand the awaitable examples correctly, mocking the obj which is an Awaitable should be returning an AsyncMock.

Comments
  • Now you know why the use of isinstance is discouraged.
  • @MarkRansom Yes it is evil. But what is the best practice to ensure the interface we pass in is CORRECT? hasattr doesn't seem to solve the gap either. Two objects may have same method names and uses the wrong object will make the test pass, I think? I guess the question's focus has shifted! Ahh.
  • Thanks Mark. I think the topic is shifted. But the problem with the error hasn't solved yet :( I will make a new post when I think of a more concrete question with respect to isinstance. Thanks!
  • You're getting the error because HelloWorld (after patching) isn't a class or a type, but a mock.MagicMock instance. As the error says, the second argument must be a class, type, or tuple of classes or types. The spec thing you refer to us for the first argument. That's what you're showing in your last example (from the docs). Why, exactly, do you wish to check whether your HelloWorld instance is an instance of an emulated type (which is, I think, impossible)?
  • "one of the many nice things about Python is that it allows "Duck Typing" where you don't care about the exact type of an object as long as it does what you want" - until it doesn't.
  • Thanks. But the first question would really be why is it throwing that error? And two, adopting this change, if two objs have the same method name, the test will pass, right?
  • I don't know why the error is thrown. It doesn't matter, soon you won't be using isinstance :) And yes, now you can pass in any object that has the method, and behaves "properly", and the test will pass.
  • Thanks. I will stop using isinstance after reading the major fallback. BUt still... if MyMomKitchenObject has say_it and programmer uses that as the input of the function... the test will still PASS, doesn't it? So how should I verify my unittest actually works? or how should I determine its correctness in my code? Just like integration test, two objs can have 99% same interface, and the system under test never uses that 1% different, and the test still passes, the system will "work without problem".
  • Don't use isinstance() is not a solution for me: I try to mock datetime.datetime and it is used in external libraries. In my case django uses it.
  • Downvote, suggesting to not use isinstance is a terrible answer. Do it right with Mock, much like this: stackoverflow.com/a/11283173/169153
  • This statement is incorrect: "HelloWorld is a Mock() object and not a class (as the error suggested)." If you catch the original TypeError and debug, you'll see that executing type(HelloWorld) will also return a <class 'mock.MagicMock'>.
  • @seanazlin I'll check it later. But why the error say that HelloWord is not a class or type? Anyway thanks for your feedback.
  • @SeanAzlin Please type on your python console follow statements: type(MagicMock()), type(MagicMock), type(object()), type(object). After that I hope you'll understand that what I had wrote is correct. Anyway if your comment about I used to write Mock instead of MagicMock, I think that is wrong but not a very big issue ... It is just a detail that I will fix. Pay more attention when you use downvote. My answer is correct and it is the only one that cover the original question without say *Hey guy! Don't do that".