Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

isinstance and Mocking

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 
like image 804
CppLearner Avatar asked Jun 21 '12 21:06

CppLearner


People also ask

What is mocking in Python?

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.

What is Pytest mocking?

Pytest-mock provides a fixture called mocker . It provides a nice interface on top of python's built-in mocking constructs. You use mocker by passing it as an argument to your test function, and calling the mock and patch functions from it.

What is mocking in Django?

When mocking in a test, we are in a certain way making fun of our software or of the function we are testing, simulating the behaviour of a specific external functionality. In Python there is a package in the standard library that helps us apply mocks during our tests.

What is MagicMock?

To begin with, MagicMock is a subclass of Mock . class MagicMock(MagicMixin, Mock) As a result, MagicMock provides everything that Mock provides and more. Rather than thinking of Mock as being a stripped down version of MagicMock, think of MagicMock as an extended version of Mock.


1 Answers

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 
like image 134
Michele d'Amico Avatar answered Oct 11 '22 12:10

Michele d'Amico