Override the Python mock Decorator

Asked

Viewed 377 times

12

I have a class TestCase where all tests, except one, need to patch the same object. I am using the 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 "overwrite" the patch made in the class, but it’s not working. When I run the tests, metodo_alvo receives the class patch.

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

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

2 answers

4

The explanation of mgibsonbr on the application order of the decorators makes sense. In the same way as a pile of decorators is applied from the bottom up. I was hoping otherwise, for some reason.

But anyway, after thinking about mocking (I’m new to the subject yet) I realized that it doesn’t make any sense to try to apply a new patch on an object that has already been mocked. If it has already been mocked, then I can do what 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 as I wanted, but it had the side effect of altering the value of metodo_alvo permanently, 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 it all worked out perfectly, but it’s got a hell of a look on its face. And Python has a context management 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):
             (....)

It does the same thing I tried with the Developer superscript, but now at execution time of the method. It worked perfectly, of course and concise.

2

That is correct, yes. In order for the class to be passed on to the decorator, it must already be built - which meant that its fields and methods already have to be ready and assigned. Thus, the decorator of the method will have 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 [if this patch is overwriting itself] is for you to 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

An alternative, if this doesn’t work for any reason (I don’t know the Python Mock), would be to create your own decorator that would "mark" the method in which it is applied, and not apply it again when applying it to the 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 "marking" may be unnecessary if the result of a previous application of patch has some distinct characteristic; it would be the result at all times of the kind MagicMock? If yes, just test this instead of using an extra attribute.)

One last alternative would be to use metaclasses, but I don’t think it would bring any benefit compared to previous methods - just more complexity.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.