24

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.

4

3 回答 3

26

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.

于 2012-08-31T18:45:36.810 回答
6

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.

于 2012-09-04T06:11:08.923 回答
6

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.

于 2014-07-04T15:25:52.687 回答