Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock bound method for introspection

I would like to test the following code

import weakref

class WeakBoundMethod:
    """
    Wrapper around a method bound to a class instance. As opposed to bare
    bound methods, it holds only a weak reference to the `self` object,
    allowing it to be deleted.

    This can be useful when implementing certain kinds of systems that
    manage callback functions, such as an event manager.

    """
    def __init__(self, meth):
        """
        Initializes the class instance. It should be ensured that methods
        passed through the `meth` parameter are always bound methods. Static
        methods and free functions will produce an `AssertionError`.

        """
        assert (hasattr(meth, '__func__') and hasattr(meth, '__self__')),\
               'Object is not a bound method.'

        self._self = weakref.ref(meth.__self__)
        self._func = meth.__func__

    def __call__(self, *args, **kw):
        """
        Calls the bound method and returns whatever object the method returns.
        Any arguments passed to this will also be forwarded to the method.

        In case an exception is raised by the bound method, it will be
        caught and thrown again to the caller of this `WeakBoundMethod` object.

        Calling this on objects that have been collected will result in
        an `AssertionError` being raised.

        """        
        assert self.alive(), 'Bound method called on deleted object.'

        try:
            return self._func(self._self(), *args, **kw)
        except Exception as e:
            raise e

    def alive(self):
        """
        Checks whether the `self` object the method is bound to has
        been collected.

        """
        return self._self() is not None

I thought about using mock from the standard library to check if the __call__ method calls the required arguments. The problem I face is that if I create a Mock object for a bound method, it does not have a __self__ or __func__ attribute. So I tried the following code:

class TestWeakBoundMEthod(unittest.TestCase):
    def setUp(self):
        self.bm = Mock(name='bound method')
        self.bm.__self__ = Mock(name='self')
        self.bm.__func__ = Mock(name='func')

    def test_call(self):
        wbm = events.WeakBoundMethod(self.bm)
        wbm()
        self.bm.__func__.assert_called_with(self.bm.__self__)
        wbm(1, 2, 3)
        self.bm.__func__.assert_called_with(self.bm.__self__, 1, 2, 3)

It works, but I feel like I'm not testing properly. I'm using too much knowledge on how the class WeakBoundMethod is working, instead of testing the actual result.

Is there a better way to mock a bound method? Should I make a Dummy class with a dummy method instead?

like image 602
Dan737 Avatar asked Oct 19 '22 19:10

Dan737


1 Answers

First of all: if you are using python-3.4 why don't use WeakMethod from weakref instead?

Anyway mock framework is a powerful but sometimes can be an overkilled approach and force you to know too much of what you want to test: the side effect can be that your test become too much coupled to the implementation.

In your case is better use real object to test it. A quite complete test of your WeakBoundMethod can be this:

class TestWeakMethod(unittest.TestCase):
    def test_call(self):
        class C():
            def c(self,*args,**kwargs):
                return (args,kwargs)
        c=C()
        wc = WeakBoundMethod(c.c)
        self.assertEqual(((1,2,3),{"a":11,"b":"33"}), wc(1,2,3,a=11,b="33"))

    def test_just_bound_method(self):
        c=object()
        self.assertRaises(Exception, WeakBoundMethod, c)
        self.assertRaises(Exception, WeakBoundMethod, object)
        def f():
            pass
        self.assertRaises(Exception, WeakBoundMethod, f)

    def test_is_really_weak(self):
        class C():
            DEAD=False
            def c(self,*args,**kwargs):
                return (args,kwargs)
            def __del__(self):
                C.DEAD = True
        c=C()
        wc = WeakBoundMethod(c.c)
        del c

        self.assertTrue(C.DEAD)
        self.assertRaises(Exception, wc)

I hope is clear enough: I love mock framework and I'm using it quite intensely but you shouldn't use it if it is not hard to create real object that can sense and report what you want to test.

like image 153
Michele d'Amico Avatar answered Oct 22 '22 23:10

Michele d'Amico