Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using python mock to count number of method calls

I'm just starting out using the python mocking framework. I'd like to just count the number of times a method gets called, without removing the effects of actually calling the method.

For example, in this simple counter example, I would like to both increment the counter and track that it was called:

import unittest
import mock


class Counter(object):
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1


class CounterTest(unittest.TestCase):
    def test_increment(self):
        c = Counter()
        c.increment()
        self.assertEquals(1, c.count)

    def test_call_count(self):

        with mock.patch.object(Counter, 'increment') as fake_increment:
            c = Counter()
            self.assertEquals(0, fake_increment.call_count)
            c.increment()
            self.assertEquals(1, fake_increment.call_count)

            # increment() didn't actually get called.
            self.assertEquals(1, c.count)  # Fails.

if __name__ == '__main__':
    unittest.main()

Is it possible to force mock to call the mocked method after it registered the call, or just signify that I want to keep the effects of the mocked function?

like image 654
Felix Avatar asked Mar 12 '14 15:03

Felix


People also ask

How do you mock method calls 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 Call_count?

call_count is used to see how many times the method is called. . call_args tells the arguments used when the method is called.

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.

What is the difference between mock and MagicMock?

Mock vs. 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.


2 Answers

Just use wraps:

c = Counter()
with mock.patch.object(Counter, 'increment', wraps=c.increment) as fake_increment:

There can be some binding problems if you initialize c later, as the function passed to wraps won't know about self.

like image 159
simonzack Avatar answered Sep 22 '22 08:09

simonzack


I'm not super experienced in mock1, but I accomplished it by using a function wrapper rather than the default MagicMock:

class FuncWrapper(object):
    def __init__(self, func):
        self.call_count = 0
        self.func = func

    def __call__(self, *args, **kwargs):
        self.call_count += 1
        return self.func(*args, **kwargs)

class CounterTest(unittest.TestCase):
    def test_call_count(self):

        c = Counter()
        new_call = FuncWrapper(c.increment)
        with mock.patch.object(c, 'increment', new=new_call) as fake_increment:
            print fake_increment
            self.assertEquals(0, fake_increment.call_count)
            c.increment()
            self.assertEquals(1, fake_increment.call_count)

            self.assertEquals(1, c.count)  # Fails.

Of course, this FuncWrapper is pretty minimal. It just counts the calls and then delegates flow control back to the original function. If you need to test other things at the same time, you'd need to add to the FuncWrapper class. I've also just patched a class instance rather than the entire class. The main reason for that is because I needed an instance method in FuncWrapper.

1In fact, I just started to learn -- Consider yourself warned ;-).

like image 44
mgilson Avatar answered Sep 22 '22 08:09

mgilson