Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: calling stop on mock patch class decorator

The Mock documentation describes a simple and elegant way of applying patches to all of the tests method inside a TestCase:

@patch('foo.bar')
@patch('foo.baz')
@patch('foo.quux')
@patch('foo.narf')
class FooTest(TestCase):

    def test_foo(self, bar, baz, quux, narf):
        """ foo """
        self.assertTrue(False) 

However, one issue I've encountered with this method is that if I'd like to call stop() on one of the patches inside one of the test methods, there doesn't appear to be anyway of getting a reference to the patcher object -- the only thing that is passed into the method is the mock objects, in this case bar, baz, quux, narf.

The only way I've found to solve this problem is to move to the pattern described in the Mock docs where the patchers are instantiated and started inside the setUp method of the TestCase and stopped inside the tearDown method. This fits my purpose, but adds a lot of extra boilerplate and isn't as elegant as the class decorator approach.

Is there another way to solve this problem?

like image 860
LiavK Avatar asked Aug 01 '14 22:08

LiavK


People also ask

Can you mock a decorator Python?

Mock the Decorator before the import of your function/module. The decorators and functions are defined at the time the module is loaded. If you do not mock before import, it will disregard the mock.

What is Side_effect in mock Python?

side_effect: A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT , the return value of this function is used as the return value.

How do I use a patch mock in Python?

How do we mock in Python? Mocking in Python is done by using patch to hijack an API function or object creation call. When patch intercepts a call, it returns a MagicMock object by default. By setting properties on the MagicMock object, you can mock the API call to return any value you want or raise an Exception .

What is the difference between MagicMock and mock?

So what is the difference between them? MagicMock is a subclass of Mock . It contains all magic methods pre-created and ready to use (e.g. __str__ , __len__ , etc.). Therefore, you should use MagicMock when you need magic methods, and Mock if you don't need them.


1 Answers

1

Say you want to temporarily restore foo.narf in a method. foo.narf is, in the context of the decorated function, a MagicMock object. This object has a _mock_wraps attribute which will be invoked when the mock is called! So at the top of your module, _narf = foo.narf, and in your test case, foo.narf._mock_wraps = _narf.

The catch is that this will only pass through to the real function, not actually swap it back, which means that some test cases will fail (e.g. if they rely on the function object actually being "itself"). And if your mock has other attributes, that could interfere (I haven't tested much) because the passthrough call to _mock_wraps() comes at the bottom of a method that first considers the other properties of the mock.

2

The patch() decorator involves each patcher (separate copies per method) being added to a list called patchings which is a field of the method itself. I.e. you can access this list as self.test_foo.patchings, and go through to find the one you want.

However, start() and stop() are not actually called when you use patch() as a decorator, and the behavior gets tricky once you start reaching in and changing it. So I wrote this context manager.

class unpatch:
    def __init__(self, name, method):
        compare = patch(name)
        self.patcher = next((
            p for p in method.patchings
            if p.target == compare.getter()
            and p.attribute == compare.attribute
        ), None)
        if self.patcher is None:
            raise ValueError(name)

    def __enter__(self):
        self.patcher.__exit__()

    def __exit__(self, *exc_info):
        self.patcher.__enter__()

Inside your test case, you use it like this:

with unpatch('foo.narf', self.test_foo):
    foo.narf()

Disclaimer: this is hacks.

like image 165
Jason S Avatar answered Sep 25 '22 14:09

Jason S