Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a base class with python mock library

Tags:

python

mocking

I try to use mock to write some unit-tests in python.

For example I have the following class:

class TCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        self.data = self.request.recv(1024).strip()

And I only want to test the handle method. Without having to assume anything about socketserver.BaseRequestHandler. I for example want to assert that handle calls recv with the argument 1024. Is it possible to do such thing with mock? I.e. replacing the base class socketserver.BaseRequestHandler with a mock? Or am I off track with that idea?


With the answer of ecatmur (thank you!) I first tried the following:

patcher = patch.object(TCPHandler, '__bases__', (Mock,))
with patcher:
    patcher.is_local = True
    handler = TCPHandler()
    handler.handle()

But now handle is not called anylonger and dir(handler) gives:

['assert_any_call', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'method_calls', 'mock_add_spec', 'mock_calls', 'reset_mock', 'return_value', 'side_effect']

type(handler) gives <class 'mock.TCPHandler'>

Which I interpret that patching the base class also turns my derived class into a mock.


I now gave another idea a try:

mock = MagicMock()
TCPHandler.handle(mock)
#assertions

However the mock seems not to be called.

like image 799
Frederick Roth Avatar asked Aug 31 '12 17:08

Frederick Roth


People also ask

Can we mock a class 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.

How do you mock an entire class?

Mock Entire Class To mock an entire class you'll need to set the return_value to be a new instance of the class. @mock.


3 Answers

You can do this by patching the derived class's __bases__:

def test_derived():
    patcher = mock.patch.object(Derived, '__bases__', (mock.Mock,))
    with patcher:
        patcher.is_local = True
        d = Derived()
        print d.foo()

The is_local hack is necessary to stop mock.patch from trying to call delattr when reversing the patch.

like image 137
ecatmur Avatar answered Nov 14 '22 18:11

ecatmur


I think the problem is really that you are trying to mock the actual code you want to test. Rather than the objects that are being called by that code. If you are interested in seeing whether the handle method calls the recv method on self.request then mock out the recv method.

def test_tcp_handler_method(self):

    handler = TCPHandler()
    handler.request = Mock()

    handler.handle()

    self.assertTrue(handler.request.recv.called)
    self.assertEqual(handler.request.recv.call_args[0], 1024)

You might have to do some extra setup in order to get handler to instantiate but the basic idea should be clear.

like image 29
aychedee Avatar answered Nov 14 '22 19:11

aychedee


I don't know if it is the best solution but I managed redefining the previous class with a different parent using type(). I built a function called patch_parent(), that returns the class with a parent mock:

from contextlib import contextmanager

@contextmanager
def patch_parent(class_):
    """
    Mock the bases
    """
    yield type(class_.__name__, (Mock,), dict(class_.__dict__))

After this, you can use the patch_parent like this:

class Bar():
   def method(self, param1, param2...):
       ...

class Foo(Bar):
   pass


>>> with patch_parent(Foo) as MockFoo:
...     f = MockFoo()
...     print f
...     print f.method()
... 
<Foo id='15488016'>
<Foo name='mock.method()' id='15541520'>
>>> s = Foo()
>>> s.method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method() takes exactly 3 arguments (1 given)

The MockFoo class still has the methods of the Foo class and it doesn't have the methods defined in the parent because the parent is now a Mock class.

like image 36
Pigueiras Avatar answered Nov 14 '22 18:11

Pigueiras