[PYTHON] Replace built-in functions with mock at test time without DI

open_repeatedly.py


import time

def open_repeatedly(path, retries=5, retry_interval=1.0):
    while True:
        try:
            return open(path)
        except OSError:
            if retries <= 0:
                raise
            if retry_interval > 0:
                time.sleep(retry_interval)
            retries -= 1

I'm not sure what to use it for, but if you have a function like this, I'd like to test its exceptional behavior. For example, if retry_interval is 0, time.sleep () will not be called.

However, it is troublesome to DI ```open () and time.sleep () . The interface gets dirty. I also want to avoid ** kwargs``.

So, I was told on Twitter `ʻunittest.mock.patch``.

test.py


import os
from tempfile import mkstemp
import unittest
from unittest.mock import call, patch, MagicMock

import open_repeatedly

class TestOpenRepeatedly(unittest.TestCase):
    def test_1(self):
        with patch('open_repeatedly.open') as mock:
            expected_ret = MagicMock()
            mock.return_value = expected_ret
            path = '/path/to/test.txt'
            f = open_repeatedly.open_repeatedly(path)
            self.assertEqual(f, expected_ret)
            mock.assert_called_with(path)

    def test_2(self):
        path = '/path/to/test.txt'
        with patch('open_repeatedly.open') as o_mock:
            o_mock.side_effect = OSError('Test')
            with patch('open_repeatedly.time.sleep') as s_mock:
                calls = [call(1.0)] * 5
                with self.assertRaises(OSError):
                    open_repeatedly.open_repeatedly(path)
                self.assertEqual(5, s_mock.call_count)
                s_mock.assert_has_calls(calls)
            self.assertEqual(6, o_mock.call_count)
            o_mock.assert_called_with(path)

    def test_3(self):
        path = '/path/to/test.txt'
        with patch('open_repeatedly.open') as o_mock:
            o_mock.side_effect = OSError('Test')
            with patch('open_repeatedly.time.sleep') as s_mock:
                with self.assertRaises(OSError):
                    open_repeatedly.open_repeatedly(
                        path, retry_interval=0)
                self.assertEqual(0, s_mock.call_count)
            self.assertEqual(6, o_mock.call_count)
            o_mock.assert_called_with(path)

    @patch('open_repeatedly.time.sleep')
    @patch('open_repeatedly.open')
    def test_4(self, o_mock, s_mock):
        path = '/path/to/test.txt'
        o_mock.side_effect = OSError('Test')
        with self.assertRaises(OSError):
            open_repeatedly.open_repeatedly(
                path, retries=0)
        self.assertEqual(0, s_mock.call_count)
        self.assertEqual(1, o_mock.call_count)
        o_mock.assert_called_with(path)


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

Decorators are easier.

$ python -m unittest
....
----------------------------------------------------------------------
Ran 4 tests in 0.005s

OK

Feeling that I implemented it It looks like it works as expected. Is it correct for how to use it?

Recommended Posts

Replace built-in functions with mock at test time without DI
Replace all at once with sed