25

I'm using Mock (http://www.voidspace.org.uk/python/mock/mock.html), and came across a particular mock case that I cant figure out the solution.

I have a function with multiple calls to some_function that is being Mocked.

def function():
    some_function(1)
    some_function(2)
    some_function(3)

I only wanna mock the first and third call to some_function. The second call I wanna to be made to the real some_function.

I tried some alternatives with http://www.voidspace.org.uk/python/mock/mock.html#mock.Mock.mock_calls, but with no success.

Thanks in advance for the help.

bslima
  • 1,097
  • 2
  • 15
  • 17
  • Does this answer your question? [Mock side effect only X number of times](https://stackoverflow.com/questions/32081457/mock-side-effect-only-x-number-of-times) – Huseyin Yagli Jun 08 '20 at 09:12

3 Answers3

33

It seems that the wraps argument could be what you want:

wraps: Item for the mock object to wrap. If wraps is not None then calling the Mock will pass the call through to the wrapped object (returning the real result and ignoring return_value).

However, since you only want the second call to not be mocked, I would suggest the use of mock.side_effect.

If side_effect is an iterable then each call to the mock will return the next value from the iterable.

If you want to return a different value for each call, it's a perfect fit :

somefunction_mock.side_effect = [10, None, 10] 

Only the first and third calls to somefunction will return 10.

However, if you do need to call the real function, but not the second time, you can also pass side_effect a callable, but I find it pretty ugly (there might be a smarter to do it):

 class CustomMock(object):

     calls = 0

     def some_function(self, arg):
         self.calls += 1
         if self.calls != 2:
             return my_real_function(arg)
         else:
             return DEFAULT

somefunction_mock.side_effect = CustomMock().some_function

     
Community
  • 1
  • 1
Antoine Lassauzay
  • 1,557
  • 11
  • 12
6

Even simpler than creating a CustomMock class :

def side_effect(*args, **kwargs):
    if side_effect.counter < 10:
        side_effect.counter += 1
        return my_real_function(arg) 
    else:
        return DEFAULT

side_effect.counter = 0
mocked_method.side_effect = side_effect
fallino
  • 1,381
  • 11
  • 9
  • I've found that `my_real_function` needed to be aliased, as in `import my_real_function as someother_name` otherwise the `side_effect` function keeps being called in an infinite recursion. – Christine Feb 10 '21 at 20:10
1

I faced the same situation today. After some hesitation I found a different way to work around it.

At first, I have a function looks like this:

def reboot_and_balabala(args):
    os.system('do some prepare here')
    os.system('reboot')
    sys.exit(0)

I want the first call to os.system be invoked, otherwise the local file is not generated, and I cannot verify it. But I really do not want the second call to os.system be invoked, lol.

At first, I have an unittest similar to:

def test_reboot_and_balabala(self):
    with patch.object(os, 'system') as mock_system:
        # do some mock setup on mock_system, this is what I looked for
        # but I do not found any easy and clear solution
        with patch.object(sys, 'exit') as mock_exit:
            my_lib.reboot_and_balabala(...)
            # assert mock invoke args
            # check generated files

But finally, I realized, after adjusting the code, I have a more better code structure, and unittests, by following way:

def reboot():
    os.system('reboot')
    sys.exit(0)

def reboot_and_balabala(args):
    os.system('do some prepare here')
    reboot()

And then we can test those code by:

def test_reboot(self):
    with patch.object(os, 'system') as mock_system:
        with patch.object(sys, 'exit') as mock_exit:
            my_lib.reboot()
            mock_system.assert_called_once_with('reboot')
            mock_exit.assert_called_once_with(0)

def test_reboot_and_balabala(self):
    with patch.object(my_lib, 'reboot') as mock_reboot:
        my_lib.reboot_and_balabala(...)
        # check generated files here
        mock_reboot.assert_called_once()

This is not a direct answer to the question. But I think this is very inspiring.

pjincz
  • 1,782
  • 1
  • 12
  • 10