StopIteration when mocking base class of own class - python

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?

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

Related

Mocking time.sleep will cause the test to fail

I wrote this test, but in order not to delay the test, I mock the time.sleep and the test will encounter an fail.
from unittest.mock import patch
from django.core.management import call_command
from django.db.utils import OperationalError
from django.test import TestCase
class CommandsTest(TestCase):
#patch('time.sleep', return_value=None)
def test_wait_for_db(self):
"""Test waiting for db"""
with patch('django.utils.connection.BaseConnectionHandler.__getitem__') as gi:
gi.side_effect = [OperationalError] * 5 + [True]
call_command('wait_for_db')
self.assertEqual(gi.call_count, 6)
By commenting on this second line(#patch), the program will run properly.
here is the error:
ERROR: test_wait_for_db (core.tests.test_commands.CommandsTest)
Test waiting for db
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/lib/python3.10/unittest/mock.py", line 1369, in patched
return func(*newargs, **newkeywargs)
TypeError: CommandsTest.test_wait_for_db() takes 1 positional argument but 2 were given
You'll need to add an argument to your test_wait_for_db.
Since you use a decorator, the mocked function is passed as argument to that function
class CommandsTest(TestCase):
#patch('time.sleep', return_value=None)
def test_wait_for_db(self, mocked_sleep):
In your test you can then test assert if indeed was called. More information here.

python unittest prevent abstract testBase class test itself

What I have:
I have a TestBase.py that I am using as an abstract test class.
from abc import abstractmethod
class TestBase:
expectData = None
def test_check_expectData(self):
if self.expectData is None:
raise NotImplementedError('you must have expectData')
#abstractmethod
def test_command_OK(self):
raise NotImplementedError('subclasses must override test_command()!')
I want others to inherite TestBase. for Example TestDemo.py
import unittest
from TestBase import TestBase
class TestDemo(unittest.TestCase, TestBase):
expectData = ["someData"]
def test_command_OK(self):
self.assertTrue(True)
if __name__ =='__main__':
unittest.main()
If I run python TestDemo.py everything works as expected. If I don't have a expectedDate I will get a NotImplementedError and if I don't have a test_command_OK method I get a NotImplementedError: subclasses must override test_command()!
Problem:
when I run the test suite from setup.py python setup.py test things break. The reason I think setup.py is running the TestBase class on its own and in TestBase class expectedData is None so it fails the test.
====================================================================== ERROR: myProject.tests.TestDemo.TestBase.test_check_expectData
---------------------------------------------------------------------- Traceback (most recent call last): File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
self.test(*self.arg) File "/Users/z002x6m/Desktop/myProject/tests/TestBase.py", line 49, in test_check_expectData
if self.expectData is None: AttributeError: TestBase instance has no attribute 'expectData'
====================================================================== ERROR: myProject.tests.TestDemo.TestBase.test_command_OK
---------------------------------------------------------------------- Traceback (most recent call last): File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
self.test(*self.arg) File "/Users/z002x6m/Desktop/myProject/tests/TestBase.py", line 57, in test_command_OK
raise NotImplementedError('subclasses must override test_command()!') NotImplementedError: subclasses must override test_command()!
If I print out print(self.__class__) in my test_check_expectData(self) method I get <class '__main__.TestDemo'> when I run TestDemo directly which is when everything works fine. I get myProject.tests.TestDemo.TestBasewhen I run python setup.py test.
Is there a way for me to tell setup.py not to run TestBase class? maybe #unittest.skipis , #unittest.skipif, #unittest.skipunless? but I don't want the subclass to skip these require tests beacuse I want all subclass to have expectData. TestBase is meant to be use as a template and force/check sub test classes have all the requirements such as expectData. but it shouldn't be run on it own. What can I do to get pass this issue? I don't mind suggestions even if it means to restrucutre my current test framework
Apparently, use SkipTest would skip the test if subclass super() the TestBase class
from unittest.case import SkipTest
def test_check_expectData(self):
if self.expectData is None:
raise SkipTest

Python mock a dependancy object

I have a python class (MyClass) I am unit testing. It has a external dependency which creates a database, I would like to mock that object and check it was called.
from entities_database import DBCreator
class MyClass():
def __init__(self):
self.db = DBCreator()
def create(self, name):
value = self.db.create_db(name)
I would like to test the init function, so see that a mock DBCreator called. And then to test the create function to check that create_db() was called with "name", and it returned "name" to value.
I am not sure how to go about this, so far I have this:
from entities_database import DBCreator
from test_unit import MyClass
import unittest
import mock
class MyClassTest(unittest.TestCase):
#mock.patch('entities_database.DBCreator', autospec=True)
def test_myclass_init(self, dbcreator_mock):
creator = mock.create_autospec(DBCreator)
dbcreator_mock.return_value = creator
myclass = MyClass()
assert(dbcreator_mock.called)
Which results in:
F
======================================================================
FAIL: test_myclass_init (unit_test.MyClassTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/paul/Data/TSC/Code/parltrack_eu/venv/lib/python3.6/site-packages/mock/mock.py", line 1305, in patched
return func(*args, **keywargs)
File "/Users/paul/Data/TSC/Code/parltrack_eu/unit_test.py", line 32, in test_myclass_init
assert(dbcreator_mock.called)
AssertionError
----------------------------------------------------------------------
Ran 1 test in 0.036s
FAILED (failures=1)
How can I fix my code?
---- UPDATE ----
Apart from the solution below with dependency injection, one can also do the following with patch, as suggested below #Goyo
from entities_database import DBCreator
from test_unit import MyClass
import unittest
import mock
class MyClassTest(unittest.TestCase):
#mock.patch('test_unit.DBCreator', autospec=True)
def test_myclass_init(self, mock_db):
'''
Test the initialization of MyClass
Test that DBCreator is initialized
'''
creator = mock.create_autospec(DBCreator)
mock_db.return_value = creator
# Assert that the DBCreator is initialised
myclass = MyClass()
assert(mock_db.called)
#mock.patch('test_unit.DBCreator', autospec=True)
def test_myclass_create(self, mock_db):
'''
Test the myclass.create() function
and assert it calls db.create_db() with the correct
argument
'''
name = 'unittest'
myclass = MyClass()
myclass.create(name)
# Assert that create_db was called with name
myclass.db.create_db.assert_called_with(name)
Patching is tricky. You have to patch the object in the same namespace where the SUT will look it up. In this case probably you want #mock.patch('test_unit.DBCreator', autospec=True).
Using dependency injection avoids this kind of problems and makes things more explicit and clear:
class MyClass():
def __init__(self, db):
self.db = db
def create(self, name):
value = self.db.create_db(name)
Then in your test:
class MyClassTest(unittest.TestCase):
def test_myclass_init(self):
db = mock.Mock()
myclass = MyClass(db)
self.assertEqual(myclass.db, db)
def test_myclass_create(self):
db = mock.Mock()
myclass = MyClass(db)
name = mock.Mock()
myclass.create(name)
myclass.db.create_db.assert_called_once_with(name)
I am not able to try with your example code. But you should try to mock create_db function not DBCreator class.

Mock modules and subclasses (TypeError: Error when calling the metaclass bases)

To compile documentation on readthedocs, the module h5py has to be mocked. I get an error which can be reproduced with this simple code:
from __future__ import print_function
import sys
try:
from unittest.mock import MagicMock
except ImportError:
# Python 2
from mock import Mock as MagicMock
class Mock(MagicMock):
#classmethod
def __getattr__(cls, name):
return Mock()
sys.modules.update({'h5py': Mock()})
import h5py
print(h5py.File, type(h5py.File))
class A(h5py.File):
pass
print(A, type(A))
class B(A):
pass
The output of this script is:
<Mock id='140342061004112'> <class 'mock.mock.Mock'>
<Mock spec='str' id='140342061005584'> <class 'mock.mock.Mock'>
Traceback (most recent call last):
File "problem_mock.py", line 32, in <module>
class B(A):
TypeError: Error when calling the metaclass bases
str() takes at most 1 argument (3 given)
What is the correct way to mock h5py and h5py.File?
It seems to me that this issue is quite general for documentation with readthedocs where some modules have to be mocked. It would be useful for the community to have an answer.
You can't really use Mock instances to act as classes; it fails hard on Python 2, and works by Python 3 only by accident (see below).
You'd have to return the Mock class itself instead if you wanted them to work in a class hierarchy:
>>> class A(Mock): # note, not called!
... pass
...
>>> class B(A):
... pass
...
>>> B
<class '__main__.B'>
>>> B()
<B id='4394742480'>
If you can't import h5py at all, that means you'll need to keep a manually updated list of classes where you return the class rather than an instance:
_classnames = {
'File',
# ...
}
class Mock(MagicMock):
#classmethod
def __getattr__(cls, name):
return Mock if name in _classnames else Mock()
This is not foolproof; there is no way to detect the parent instance in a classmethod, so h5py.File().File would result in yet another 'class' being returned even if in the actual implementation that was some other object instead. You could partially work around that by creating a new descriptor to use instead of the classmethod decorator that would bind to either the class or to an instance if one is available; that way you at least would have a context in the form of self._mock_name on instances of your Mock class.
In Python 3, using MagicMock directly without further customisation works when used as a base class:
>>> from unittest.mock import MagicMock
>>> h5py = MagicMock()
>>> class A(h5py.File): pass
...
>>> class B(A): pass
...
but this is not really intentional and supported behaviour; the classes and subclasses are 'specced' from the classname string:
>>> A
<MagicMock spec='str' id='4353980960'>
>>> B
<MagicMock spec='str' id='4354132344'>
and thus have all sorts of issues down the line as instanciation doesn't work:
>>> A()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/unittest/mock.py", line 917, in __call__
return _mock_self._mock_call(*args, **kwargs)
File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/unittest/mock.py", line 976, in _mock_call
result = next(effect)
StopIteration

'SentinelObject' object has no attribute 'reset_mock'

I am not sure why the following code is not working. I am using the Mock framework. Anyone could explain it to me?
The error I get is this:
$ python test_mock.py
Calls the mock object method. not a real one. ... ERROR
======================================================================
ERROR: Calls the mock object method. not a real one.
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_mock.py", line 37, in test_is_method_called
self.sut.do_something()
File "test_mock.py", line 21, in do_something
self.__obj.method()
File "build/bdist.linux-i686/egg/mock.py", line 365, in __getattr__
self._children[name] = self._get_child_mock(parent=self, name=name, wraps=wraps)
File "build/bdist.linux-i686/egg/mock.py", line 458, in _get_child_mock
return klass(**kw)
File "build/bdist.linux-i686/egg/mock.py", line 282, in __init__
self.reset_mock()
File "build/bdist.linux-i686/egg/mock.py", line 303, in reset_mock
self._return_value.reset_mock()
AttributeError: 'SentinelObject' object has no attribute 'reset_mock'
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
The code is:
import unittest
import mock
class Fubar ():
def __init__(self):
pass
def method (self):
print "I am a Stup!d Monkey!"
class Monkey ():
def __init__(self, obj):
self.__obj = obj
def do_something (self):
if not isinstance(self.__obj, Fubar):
raise RuntimeError
self.__obj.method()
class TestMoneky (unittest.TestCase):
def setUp(self):
self.mock_obj = mock.Mock(name="Mock object", spec=["method"])
self.sut = Monkey(self.mock_obj)
def tearDown(self):
pass
def test_is_method_called (self):
"""Calls the mock object method. not a real one."""
with mock.patch("__builtin__.isinstance") as mock_inst:
mock_inst.return_value = True
self.sut.do_something()
self.assertTrue(self.mock_obj.method.called)
def main ():
"""Simple runner."""
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestMoneky))
unittest.TextTestRunner(verbosity=2).run(suite)
if __name__ == '__main__':
main()
The problems (sadly) is trivial. I am patching isinstance() to always return True. So, somewhere in the bowels of the module, something is asking whether my mock object is a Sentinel and (since I am overriding the return value), True is returned. Thus the wrong internal behaviour is exhibited.
The solution is therefore to not patch isinstance() but instead provide the mock with a spec that matches the class it is supposed to match:
def setUp(self):
self.mock_obj = mock.Mock(name="Mock object", spec=Fubar)
self.sut = Monkey(self.mock_obj)
def test_is_method_called (self):
"""Calls the mock object method. not a real one."""
self.sut.do_something()
self.assertTrue(self.mock_obj.method.called)
Can anyone see a way to do this without coupling the mock to Fubar???...

Categories