Search code examples
pythonpython-unittest

Re-use Patch in Python Test


Not an expert. If I patch a module's method, is it possible to re-use the same patch in other methods of the TestCase?


    def load(**kwargs):
      return 1

    def load2(**kwargs):
      return2

    @patch.multiple('module',
                    get_data=MagicMock(side_effect=load),
                    headers=MagicMock(return_value=""))
    def test_get_some_method(self):
      # here is ok

    @patch.multiple('module',
                    get_data=MagicMock(side_effect=load2),
                    headers=MagicMock(return_value=""))
    def test_get_other_method(self):
      # here I get an exception:'load1() takes 0 positional arguments but 1 was given'

EDIT Maybe it is better to use return_value instead of side_effect...


Solution

  • Yes, you can use the TestCase.setUpClass class method for this. The "patcher" returned by patch needs to be properly stopped though, if you don't use it in the form of a decorator or context manager. Thus you should always include that call in TestCase.tearDownClass.

    Here is a little demo for you.

    code.py

    class Spam:
        def __init__(self, x: float) -> None:
            self._x = x
    
        def get_x(self) -> float:
            return self._x
    
        def get_x_times_2(self) -> float:
            return self.get_x() * 2
    
        def get_x_squared(self) -> float:
            return self.get_x() ** 2
    
        def print_x(self) -> None:
            print(self.get_x())
    

    Say we wanted to test all methods that call get_x and with the exact same mock object (for some reason).


    test.py

    from unittest import TestCase
    from unittest.mock import MagicMock, patch
    
    from . import code
    
    
    class SpamTestCase(TestCase):
        get_x_patcher = None
        mock_get_x: MagicMock = None
    
        @classmethod
        def setUpClass(cls) -> None:
            cls.get_x_patcher = patch.object(code.Spam, "get_x")
            cls.mock_get_x = cls.get_x_patcher.start()
    
        @classmethod
        def tearDownClass(cls) -> None:
            cls.get_x_patcher.stop()
    
        def setUp(self) -> None:
            self.spam = code.Spam(3.14)
    
        def test_get_x_times_2(self) -> None:
            self.mock_get_x.return_value = 5
            self.assertEqual(10, self.spam.get_x_times_2())
    
        def test_get_x_squared(self) -> None:
            self.mock_get_x.return_value = 4
            self.assertEqual(16, self.spam.get_x_squared())
    
        @patch.object(code, "print")
        def test_print_x(self, mock_print: MagicMock) -> None:
            self.mock_get_x.return_value = 10.5
            self.assertIsNone(self.spam.print_x())
            mock_print.assert_called_once_with(10.5)
    

    However, I don't really see the use case for this. Using regular setUp and tearDown should be enough to facilitate consistency across all test methods, if you need that and don't want to repeat yourself in multiple decorators/context managers. The mock objects will not be literally the same, but created the same way.

    Hope this helps.