title: Mocking a class method and passing self argument to the Mock's side effect
body: <p>I am trying to patch a single method in an existing class within a unit test. The class to be patched is:</p>
  
  <pre class="lang-py prettyprint-override"><code>class Example:
      def __init__(self: "Example", id: int) -&gt; None:
          self.id : int = id
          self._loaded : bool = False
          self._data : typing.Union[str,None] = None
  
      def data(self: "Example") -&gt; str:
          if not self._loaded:
              self.load()
          return self._data
  
      def load(self: "Example") -&gt; None:
          self._loaded = True
          # some expensive computations
          self._data = f"real_data{self.id}"
  </code></pre>
  
  <p>So that instead of calling the <code>self.load()</code> a <code>unittest.mock.Mock</code> is called with the <code>mocked_load</code> function (below) as the side effect:</p>
  
  <pre class="lang-py prettyprint-override"><code>def mocked_load(self: "Example") -&gt; None:
      # mock the side effects of load without the expensive computation.
      self._loaded = True
      self._data = f"test_data{self.id}"
  </code></pre>
  
  <p>The first attempt was:</p>
  
  <pre class="lang-py prettyprint-override"><code>@unittest.mock.patch.object(Example, "load", new = mocked_load)
  def test_data__patch_new(
      self: "TestExample",
  ) -&gt; None:
      example1 = Example(id=1)
      example2 = Example(id=2)
  
      data1_1 = example1.data()
      self.assertEqual(data1_1, "test_data1")
  
      data2_1 = example2.data()
      self.assertEqual(data2_1, "test_data2")
  
      data1_2 = example1.data()
      self.assertEqual(data1_2, "test_data1")
  
      data2_2 = example2.data()
      self.assertEqual(data2_2, "test_data2")
  </code></pre>
  
  <p>This works but just replaces the <code>Example.load</code> function with the <code>mocked_load</code> function rather than wrapping it in a <code>Mock</code>; so, although it does pass you cannot extend the test to assert how many times the patched <code>Example.load</code> method was called. It is not the solution I am looking for.</p>
  
  <p>The second attempt was:</p>
  
  <pre class="lang-py prettyprint-override"><code>@unittest.mock.patch.object(Example, "load")
  def test_data__patch_side_effect(
      self: "TestExample",
      patched_load: unittest.mock.Mock
  ) -&gt; None:
      patched_load.side_effect = mocked_load
  
      example1 = Example(id=1)
      example2 = Example(id=2)
  
      self.assertEqual(patched_load.call_count, 0)
  
      data1_1 = example1.data()
      self.assertEqual(data1_1, "test_data1")
      self.assertEqual(patched_load.call_count, 1)
  
      data2_1 = example2.data()
      self.assertEqual(data2_1, "test_data2")
      self.assertEqual(patched_load.call_count, 2)
  
      data1_2 = example1.data()
      self.assertEqual(data1_2, "test_data1")
      self.assertEqual(patched_load.call_count, 2)
  
      data2_2 = example2.data()
      self.assertEqual(data2_2, "test_data2")
      self.assertEqual(patched_load.call_count, 2)
  </code></pre>
  
  <p>This fails with the exception:</p>
  
  <pre><code>Traceback (most recent call last):
    File "/usr/lib/python3.8/unittest/mock.py", line 1325, in patched
      return func(*newargs, **newkeywargs)
    File "my_file.py", line 65, in test_data__patch_side_effect
      data1_1 = example1.data()
    File "my_file.py", line 13, in data
      self.load()
    File "/usr/lib/python3.8/unittest/mock.py", line 1081, in __call__
      return self._mock_call(*args, **kwargs)
    File "/usr/lib/python3.8/unittest/mock.py", line 1085, in _mock_call
      return self._execute_mock_call(*args, **kwargs)
    File "/usr/lib/python3.8/unittest/mock.py", line 1146, in _execute_mock_call
      result = effect(*args, **kwargs)
  TypeError: mocked_load() missing 1 required positional argument: 'self'
  </code></pre>
  
  <p>The final attempt was:</p>
  
  <pre class="lang-py prettyprint-override"><code>@unittest.mock.patch.object(Example, "load")
  def test_data__patch_multiple_side_effect(
      self: "TestExample",
      patched_load: unittest.mock.Mock
  ) -&gt; None:
      example1 = Example(id=1)
      example2 = Example(id=2)
  
      side_effect1 = lambda: mocked_load( example1 )
      side_effect2 = lambda: mocked_load( example2 )
  
      patched_load.side_effect = side_effect1
  
      self.assertEqual(patched_load.call_count, 0)
  
      data1_1 = example1.data()
      self.assertEqual(data1_1, "test_data1")
      self.assertEqual(patched_load.call_count, 1)
  
      patched_load.side_effect = side_effect2
  
      data2_1 = example2.data()
      self.assertEqual(data2_1, "test_data2")
      self.assertEqual(patched_load.call_count, 2)
  
      patched_load.side_effect = side_effect1
  
      data1_2 = example1.data()
      self.assertEqual(data1_2, "test_data1")
      self.assertEqual(patched_load.call_count, 2)
  
      patched_load.side_effect = side_effect2
  
      data2_2 = example2.data()
      self.assertEqual(data2_2, "test_data2")
      self.assertEqual(patched_load.call_count, 2)
  </code></pre>
  
  <p>This "works" but its very fragile as the <code>self</code> arguments are hardcoded into the <code>lambda</code> functions and the mock's <code>side_effect</code> needs to be swapped to match each call.</p>
  
  <p>The full minimal representative example is:</p>
  
  <pre class="lang-py prettyprint-override"><code>import typing
  import unittest
  import unittest.mock
  
  class Example:
      def __init__(self: "Example", id: int) -&gt; None:
          self.id : int = id
          self._loaded : bool = False
          self._data : typing.Union[str,None] = None
  
      def data(self: "Example") -&gt; str:
          if not self._loaded:
              self.load()
          return self._data
  
      def load(self: "Example") -&gt; None:
          self._loaded = True
          # some expensive computations
          self._data = f"real_data{self.id}"
  
  def mocked_load(self: "Example") -&gt; None:
      # mock the side effects of load without the expensive computation.
      self._loaded = True
      self._data = f"test_data{self.id}"
  
  class TestExample( unittest.TestCase ):
      @unittest.mock.patch.object(Example, "load", new = mocked_load)
      def test_data__patch_new(
          self: "TestExample",
      ) -&gt; None:
          # This works but just replaces the Example.load function with another
          # rather than wrapping it in a Mock; so you cannot assert how many
          # times the patched method was called.
  
          example1 = Example(id=1)
          example2 = Example(id=2)
  
          data1_1 = example1.data()
          self.assertEqual(data1_1, "test_data1")
  
          data2_1 = example2.data()
          self.assertEqual(data2_1, "test_data2")
  
          data1_2 = example1.data()
          self.assertEqual(data1_2, "test_data1")
  
          data2_2 = example2.data()
          self.assertEqual(data2_2, "test_data2")
  
      @unittest.mock.patch.object(Example, "load")
      def test_data__patch_side_effect(
          self: "TestExample",
          patched_load: unittest.mock.Mock
      ) -&gt; None:
          # This fails as the self argument is not passed to the side_effect
          # function.
  
          patched_load.side_effect = mocked_load
  
          example1 = Example(id=1)
          example2 = Example(id=2)
  
          self.assertEqual(patched_load.call_count, 0)
  
          data1_1 = example1.data()
          self.assertEqual(data1_1, "test_data1")
          self.assertEqual(patched_load.call_count, 1)
  
          data2_1 = example2.data()
          self.assertEqual(data2_1, "test_data2")
          self.assertEqual(patched_load.call_count, 2)
  
          data1_2 = example1.data()
          self.assertEqual(data1_2, "test_data1")
          self.assertEqual(patched_load.call_count, 2)
  
          data2_2 = example2.data()
          self.assertEqual(data2_2, "test_data2")
          self.assertEqual(patched_load.call_count, 2)
  
      @unittest.mock.patch.object(Example, "load")
      def test_data__patch_multiple_side_effect(
          self: "TestExample",
          patched_load: unittest.mock.Mock
      ) -&gt; None:
          # This passes but feels (very) wrong as you have to have change the
          # side_effect each time you call the function and relies on hardcoding
          # the class instances being passed as "self".
  
          example1 = Example(id=1)
          example2 = Example(id=2)
  
          side_effect1 = lambda: mocked_load( example1 )
          side_effect2 = lambda: mocked_load( example2 )
  
          patched_load.side_effect = side_effect1
  
          self.assertEqual(patched_load.call_count, 0)
  
          data1_1 = example1.data()
          self.assertEqual(data1_1, "test_data1")
          self.assertEqual(patched_load.call_count, 1)
  
          patched_load.side_effect = side_effect2
  
          data2_1 = example2.data()
          self.assertEqual(data2_1, "test_data2")
          self.assertEqual(patched_load.call_count, 2)
  
          patched_load.side_effect = side_effect1
  
          data1_2 = example1.data()
          self.assertEqual(data1_2, "test_data1")
          self.assertEqual(patched_load.call_count, 2)
  
          patched_load.side_effect = side_effect2
  
          data2_2 = example2.data()
          self.assertEqual(data2_2, "test_data2")
          self.assertEqual(patched_load.call_count, 2)
  
  
  if __name__ == '__main__':
      unittest.main()
  </code></pre>
  
  <p>How can I "fix" my second attempt (or give an alternate method) to patch the <code>Example.load</code> function with a <code>Mock</code> so that a <code>side_effect</code> function can be called that has side effects on <code>self</code>?</p>