Python mocking: How to test the number of calls on a recursive function?

只愿长相守 提交于 2021-01-27 23:36:32

问题


I have a recursive function living in a module called test_module

import requests    

def send_msg(msg, retries=0):
    try:
        # send the message here, e.g. a http request
        response = requests.get("http://www.doesnotexist98734.com")
        # if url does not exist raise an exception
    except Exception as e:
        if retries == 0:
            raise e
        else:
            return send_msg(msg, retries=retries-1)

My question is how can I write a unittest that checks the send_msg function is called n times when I set retries = n. I was playing around with the mock module (I'm using python 2.7) and I think I want something a bit like this,

import mock, unittest

class MyUnitTest(unittest.TestCase):

    @mock.patch('test_module.send_msg')
    def test_send_msg_tries_n_times(self, mock_send_msg):
        with self.assertRaises(Exception):
            mock_send_msg("hello", retries=3)
        self.assertEqual(mock_send_msg.call_count, 4) # initial call + 3 retries

However since I have mocked the function it won't call the real function so I don't get an exception nor does it call itself recursively...


回答1:


You can't mock the function under test. You want to test for expected results, not if the function used recursion correctly.

Mock the request.get() call, and have it always produce an exception. Then count how often your mock was called.

@mock.patch('requests.get')
def test_send_msg_tries_n_times(self, req_get_mock):
    req_get_mock.side_effect = Exception
    with self.assertRaises(Exception):
        send_msg("hello", retries=3)
    self.assertEqual(req_get_mock.call_count, 4)  # 1 initial call + 3 retries

If in future you want to avoid using recursion and want to use iteration instead, your test still will work as it validates the behaviour, not the specific implementation. You can safely refactor the function-under-test.




回答2:


I find verifying the recursive call structure of a function to be an incredibly useful feature of a unit test. This can be accomplished easily by using the side_effect argument in Mock, instead of using the patch decorator.

side_effect accepts a function, which will be passed the same arguments as the Mock. The the return value of the Mock is also the return value of the side_effect function. What this means is that we can pass the original function to the Mock as a side_effect, and effectively have a Mock wrapper around our recursive function.

For example:

def test_send_msg_tries_n_times(self, mock_send_msg):
    test_module.send_msg = Mock(side_effect=test_module.send_msg)

    test_module.send_msg("hello", retries=3)
    test.module.send_msg.assert_has_calls([
        call("hello", retries=3),
        call("hello", retries=2),
        call("hello", retries=1),
        call("hello", retries=0),
    ])


来源:https://stackoverflow.com/questions/31993268/python-mocking-how-to-test-the-number-of-calls-on-a-recursive-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!