48
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
martineau
  • 119,623
  • 25
  • 170
  • 301
CppLearner
  • 16,273
  • 32
  • 108
  • 163
  • 3
    Now you know why the use of `isinstance` is discouraged. – Mark Ransom Jun 21 '12 at 21:12
  • 1
    @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. – CppLearner Jun 21 '12 at 21:22
  • That's the point - 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. You might need to take some care in method naming to make sure you don't define the same name with two different meanings, but it brings great flexibility to the code in the end. It's a feature, not a bug. – Mark Ransom Jun 21 '12 at 21:35
  • 1
    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! – CppLearner Jun 21 '12 at 21:44
  • 3
    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)? – Magnus Lie Hetland Mar 18 '13 at 14:11
  • 3
    "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. – spinkus Jul 16 '16 at 04:46
  • In my case, I needed to mock datetime.datetime because I want to 'now' returns a specific date. But I have some libs that check if some value is datetime.datetime and I don't know how can I solve this. I need the inverse: "if instance(some_value, datetime.datetime)" but datetime.datetime is my mock. spec do not resolve. Can somebody help me? – ohrlando Mar 12 '21 at 13:35
  • @CppLearner, I have added an answer in which I propose an alternative of mocking `isinstance` in the particular module that it is used. – lmiguelvargasf May 21 '21 at 13:17
  • mock (MagicMock) object can be used as the first arg, but can not be used as the second arg in `isinstance()` check because `__instancecheck__` is officially unsupported magic method (by design). But `mock.NonCallableMagicMock` can be used as the second arg (see the answer below by klenwell). – Александр Apr 10 '23 at 10:07

11 Answers11

75

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
Nathaniel Ford
  • 20,545
  • 20
  • 91
  • 102
Michele d'Amico
  • 22,111
  • 8
  • 69
  • 76
  • 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 . – Sean Azlin Feb 04 '15 at 22:09
  • @seanazlin I'll check it later. But why the error say that HelloWord is not a class or type? Anyway thanks for your feedback. – Michele d'Amico Feb 05 '15 at 08:26
  • @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". – Michele d'Amico Feb 05 '15 at 09:47
  • I think I see your point. type(HelloWorld) returns classobj but type(MagicMock(spec=HelloWorld)) returns mock.MagicMock. When previously debugging in the context of @mock.patch, though, I was seeing `type(HelloWorld)` return ``, not `mock.MagicMock`. The OP was seeing the same thing. Your original solution as-written seems to imply that an output from type(object) of `` means that the object would be allowed as the 2nd arg of isinstance() when the OP already stated that it isn't. Am I still misunderstanding your solution? – Sean Azlin Feb 05 '15 at 21:07
  • @SeanAzlin What I mean is that the output of `type(MagicMock())` (so the type of a `MegicMock` **object instance**) is exactly ``. Anyway, even it *seams* a class it is not a class but an object when you try to use it in isinstance. This ambiguity is removed in python 3 where isinstace take just `type` or tuple of `type`s. In python class and object concepts are really close... When you use `patch` you replace the original reference by a new object (`MagicMock` as default) event if it is a class... class are just functions that return an object. – Michele d'Amico Feb 05 '15 at 21:30
  • @SeanAzlin My original solution say that if in some place you have something like `isinstance(o,MyClass)` you cannot `patch` `MyClass` reference of isinstance call anymore. That means you need to find some other ways like use mock object directly. – Michele d'Amico Feb 05 '15 at 21:39
  • @SeanAzlin If you need other clarifications please ask... otherwise remove the downvote. – Michele d'Amico Feb 05 '15 at 21:53
  • 1
    Michele I see your point now. Can your solution be summarized as follows: 1) Don't try to mock the `HelloWorld` class and then pass that as the 2nd arg to `isinstance`. That can't be made to work, and 2) instead, pass in a mocked object (ultimately `hw_obj ` in the example), made with `MagicMock(spec=HelloWorld)`, **as the 1st arg** to `isinstance`. That will pass the isinstance check. If that's the gist then I think your solution is a fine one and I'll happily +1 but I would recommend emphasizing that the MagicMock(spec=HelloWorld) object needs to be passed in to `isinstance` as the 1st arg. – Sean Azlin Feb 06 '15 at 02:13
  • `spec=` doesn't seem to be working for me. I'm setting the spec on the appropriate mock; the spec is correct; but `isinstance` is still throwing errors. – Nic Jun 24 '19 at 19:15
17

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)
Community
  • 1
  • 1
klenwell
  • 6,978
  • 4
  • 45
  • 84
4

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)
Eugene Kovalev
  • 3,407
  • 1
  • 15
  • 17
2

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)
Andriy Ivaneyko
  • 20,639
  • 6
  • 60
  • 82
2

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.

lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
0

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')
Sean Azlin
  • 886
  • 7
  • 21
  • What about if you **need** mock the class because lot of internal methods cannot used in testing context? The question speaking about *Instance and Mocking* and not *Mocking a method* and where are the answers to Q1 and Q2 ? I don't downvote your answer like you did but maybe you need to pay more attention to questions before file answers. About your answer from a technical point of view why use `patch.object` you don't really need it. You can use `@patch('__main__.HelloWorld.say_it', return_value='I am fake')` that is more concise and simpler to read. – Michele d'Amico Feb 05 '15 at 10:12
  • @Micheled'Amico Fine points. What I'm proposing is only a potential solution (or maybe work-around is a better term) for some situations, such as when one is unit testing a function that uses isinstance() and that calls the method of a class that can't be mocked because of this issue. I think that work-around may be more appealing to some than previously suggested solutions. It worked for me in my situation. – Sean Azlin Feb 05 '15 at 20:21
  • What you cannot do when you have `isinstance()` call is just patch the class, but you can use a mock for the object. BTW: pay attention when you use `patch.object`: use it only when you really need it or lot of time you will not understand why your patch doesn't work. (Consider to remove the downvote from my answer because your observation are wrong) – Michele d'Amico Feb 05 '15 at 20:47
  • Hey.... I know that if you click up arrow that not become 0 but 1. Anyway you had to do the wrong thing and you must fix it..... whatever it is – Michele d'Amico Feb 05 '15 at 21:06
0

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

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.

Давид Шико
  • 362
  • 1
  • 4
  • 13
0

The way I found to pass isinstance() while magic-mocking an instance (with all attributes and methods) is to modify the dunder class property of the mocked instance:

class Foo(): pass

foo = MagicMock()
foo.__class__ = Foo

isinstance(foo, Foo)  # True
foo.bar = 7  # works
a = foo.baz()  # MagicMock

Hope this helps someone else!

Pilz
  • 1
-1

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

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.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • 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? – CppLearner Jun 21 '12 at 21:11
  • 1
    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. – Ned Batchelder Jun 21 '12 at 21:15
  • 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". – CppLearner Jun 21 '12 at 21:19
  • Usually, an unittest is not about checking that a method exists, but checking whether it returns what you expect or not. – Thomas Orozco Jun 21 '12 at 21:33
  • Thanks Thomas. But that's not what I was asking. Unittest does the coverage, and using mock it can help us to isolate dependencies from the system under test. But we want to know what happen if someone pass in class X rather than class Y. Is it a good practice to check that in the code? I don't know... I will think for a while and then make a new post on this subject. – CppLearner Jun 21 '12 at 21:46
  • @CppLearner Yes, I understood that. What I meant is that Python usually encourages Duck Typing, that is, if your object behaves like you expect it to behave, then it's OK. So, your unittests should focus on the behavior. If someone passes a class that doesn't behave like it should, then their unittests should fail. – Thomas Orozco Jun 21 '12 at 22:17
  • 3
    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. – guettli Jun 11 '14 at 11:22
  • @CppLearner I post a answer that fit exactly your question. – Michele d'Amico Oct 28 '14 at 07:46
  • 7
    Downvote, suggesting to not use `isinstance` is a terrible answer. Do it right with Mock, much like this: http://stackoverflow.com/a/11283173/169153 – Urda Jan 21 '16 at 18:23
  • 1
    Upvote. I came to this question because I wanted to know how to test `isinstance`. I recognize that Ned's answer does not address the OP's question. However, his answer did help me recognize that my code would be better with `try` instead of `isinstance`. – lemi57ssss Oct 16 '17 at 10:36