Make decorator override of Python mock

11

I have a class TestCase where all the tests, except one, need to do the same patch of an object. I'm using Python Mock , and I did the following:

@mock.patch('metodo_alvo', mock.Mock(return_value=1))
class Tests(TestCase):

    @mock.patch('metodo_alvo', mock.Mock(return_value=2))
    def test_override(self):
        (....)

The idea is that the decorator in test_override "overwrites" the patch made in the class, but it is not working. When I run the tests, metodo_alvo gets the class patch.

After much debugging, I discovered that when python builds the test suite, the decorator in test_override (method) is called before decorator in Tests (the class) , and as mock applies patches in that order, the class decorator overrides the behavior of the method decorator.

Is that right? I expected otherwise, and now I'm not sure how to override a patch in the class. Maybe use with ?

    
asked by anonymous 08.01.2014 / 23:19

2 answers

3

The explanation of mgibsonbr about the order of application of the decorators makes sense. In the same way a stack of decorators is applied from the bottom up. I was expecting otherwise for some reason.

But anyway, after thinking about mocking (I'm new to the subject yet) I realized that it makes no sense to try applying a new patch on an object that has already been mocked. If it has already been mockado, then I can do whatever I want with it, which opens up a range of possibilities! First I tried:

@mock.patch('metodo_alvo', mock.Mock(return_value=1))
class Tests(TestCase):

    def test_override(self):
         metodo_alvo.return_value = 2
         (....)

It worked the way I wanted it, but it had the side effect of permanently changing the value of metodo_alvo , for all subsequent test cases. So obviously I tried:

@mock.patch('metodo_alvo', mock.Mock(return_value=1))
class Tests(TestCase):

    def test_override(self):
         metodo_alvo.return_value = 2
         (....)
         metodo_alvo.return_value = 1

And then everything worked perfectly, but it has a pretty gambiarra face. And Python has a context manager that works very well in this situation, and for which Mock supports:

@mock.patch('metodo_alvo', mock.Mock(return_value=1))
class Tests(TestCase):

    def test_override(self):
         with mock.patch('metodo_alvo', mock.Mock(return_value=2):
             (....)

Do the same thing I tried with the override of the decorator, but now at runtime method. It worked perfectly, it's clear and concise.

    
09.01.2014 / 14:34
1

That's right, yes. In order for the class to be passed on to the decorator, it must already be built - which means that its fields and methods must already be ready and assigned. This way, the method decorator has already been executed:

>>> def decorador(x):
...   print 'decorando ' + str(x)
...   return x
...
>>> @decorador
... class Foo(object):
...   @decorador
...   def bar():
...     pass
...
decorando <function bar at 0x00C7C2F0>
decorando <class '__main__.Foo'>

The simplest solution I can propose is if you assign the particular method only after you have created the class:

@mock.patch('metodo_alvo', mock.Mock(return_value=1))
class Tests(TestCase):
    ...

@mock.patch('metodo_alvo', mock.Mock(return_value=2))
def test_override(self):
    (....)

Tests.test_override = test_override

Alternatively, if that does not work for any reason (I do not know Python Mock), you would create your own decorator that would "tag" the method in which it is applied, and not apply again at the time of applying it to class:

from types import FunctionType

def meu_patch(f, *args, **kwargs):
    if isinstance(f, FunctionType):
        ret = mock.patch(f, *args, **kwargs) # Aplica o patch ao método
        ret.__marcado__ = True               # Marca-o, para que o patch não seja reaplicado
        return ret
    elif isinstance(f, type):
        for k,v in f.__dict__.items():
            if isinstance(v, FunctionType) and not getattr(v, '__marcado__', False):
                f[k] = mock.patch(v, *args, **kwargs) # Aplica o patch aos métodos que não
                                                      # foram previamente aplicados
    return f

(note: this "markup" is unnecessary if the result of a previous application of patch has some distinct characteristic, would it be the always result of type MagicMock ? If so, just test this instead of using an extra attribute.)

One last alternative would be to use metaclasses, but I do not think it would bring any benefit over previous methods - just more complexity.

    
09.01.2014 / 02:10