Search code examples
pythonpython-unittestpython-unittest.mockmagicmockstopiteration

StopIteration when mocking base class of own class


In a rather complex test scenario I need to mock the base class of one of my own classes and instantiate the latter several times. When I do that my test errors with a StopIteration exception. Here's what my scenario boils down to in this respect:

Code under test (my_class.py):

from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session

class MySession(OAuth2Session):
    pass

class MyClass:
    def init(self, x):
        self.x = x
        client = BackendApplicationClient(client_id=x)
        self.session = MySession(client=client)

Test code (test_mock.py):

import unittest
from unittest.mock import patch

with patch('requests_oauthlib.OAuth2Session') as MockSession:
    from my_class import MyClass

cls = MyClass()

class MockTest(unittest.TestCase):

    def test_mock_1(self):
        cls.init(1)
        self.assertIsNotNone(cls.session)

    def test_mock_2(self):
        cls.init(2)
        self.assertIsNotNone(cls.session)

Test result:

$ python -m unittest test_mock
.E
======================================================================
ERROR: test_mock_2 (test_mock.MockTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "...\test_mock.py", line 16, in test_mock_2
    cls.init(2)
  File "...\my_class.py", line 11, in init
    self.session = MySession(client=client)
  File "C:\Python39\lib\unittest\mock.py", line 1093, in __call__
    return self._mock_call(*args, **kwargs)
  File "C:\Python39\lib\unittest\mock.py", line 1097, in _mock_call
    return self._execute_mock_call(*args, **kwargs)
  File "C:\Python39\lib\unittest\mock.py", line 1154, in _execute_mock_call
    result = next(effect)
StopIteration

----------------------------------------------------------------------
Ran 2 tests in 0.003s

FAILED (errors=1)

I have debugged into the unittest.mock.MagicMock class but I can't figure out what's going on. In MagicMock's _execute_mock_call() method I noticed that self.side_effect is a tuple iterator object and when next() is called on that in the second test (test_mock_2) it results in the StopIteration.

Both tests run "OK" if I don't use the MySession subclass, i.e. self.session = OAuth2Session(client=client) in MyClass' init() method. (But that's just not how the real code under test works...)

Any ideas anyone?


Solution

  • You should mock class which you directly use, because your custom class inherit Mock and next starts unexpected behavior.

    Rewrite path method to your custom class.

    import unittest
    from unittest.mock import patch
    
    with patch('my_class.MySession') as MockSession:
        from my_class import MyClass