python unittest prevent abstract testBase class test itself - python

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

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.

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?
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

Unexpected behavior from unittest.mock.patch

What is wrong with my code below?
I'm expecting assert call_func_once_with("b") to throw an error as call_func was passed 'a'. I confimed that the function was indeed called once and with argument 'a'.
from unittest.mock import Mock, patch
def call_func(x):
pass
#patch("__main__.call_func")
def test_call_func(call_func):
call_func("a")
assert call_func.called_once_with("b")
assert call_func.called == 1
print(call_func.call_args)
test_call_func()
Output:
call('a')
You're not the first person to notice strange things with these types of assertions (see Magic mock assert_called_once vs assert_called_once_with weird behaviour)
For what it's worth, I can only advise that you try to create a test class which inherits from unittest.TestCase and then use the assertEqual method to get more consistent test behaviour:
import unittest
from unittest.mock import patch, call
def call_func(x):
pass
class MyTests(unittest.TestCase):
#patch("__main__.call_func")
def test_call_func(self, call_func_mock):
call_func_mock("a")
# assert call_func_mock.called == 1
# assert call_func_mock.called_once_with("b")
self.assertEqual(call_func_mock.call_count, 1)
self.assertEqual(call_func_mock.call_args_list[0], call("b"))
print(call_func_mock.call_args)
unittest.main()
This gives the following (expected) results:
F
======================================================================
FAIL: test_call_func (__main__.MyTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Python36\lib\unittest\mock.py", line 1179, in patched
return func(*args, **keywargs)
File "C:/scratch.py", line 16, in test_call_func
self.assertEquals(call_func_mock.call_args_list[0], call("b"))
AssertionError: call('a') != call('b')
----------------------------------------------------------------------
Ran 1 test in 0.003s
FAILED (failures=1)
Process finished with exit code 1

AttributeError: None does not have the attribute 'print'

So I am practicing some unit test and I am trying to check for an output that is within a For Loop. Here is my run code
def main():
for i in range(100):
print("Argh!")
Pretty basic, now here is my test code.
import unittest
from unittest import mock # possibly "from unittest import mock" depending on version.
from RunFile import main
class TestMain(unittest.TestCase):
def test_main(self):
with mock.patch.object(main(), 'print') as mock_print:
main()
expected_calls = [mock.call('Argh!') for _ in range(100)]
mock_print.assert_has_calls(expected_calls)
if __name__ == '__main__':
unittest.main()
Here is the error message I get back. I'm not to sure how to resolve this.
UPDATED: Here is the full trace back
======================================================================
ERROR: test_main (__main__.TestMain)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:/Users/jsalce/Documents/Testsuites/IfStatements/Testsuite.py", line 9, in test_main
with mock.patch.object(RunFile, 'print') as mock_print:
File "C:\Python33\lib\unittest\mock.py", line 1148, in __enter__
original, local = self.get_original()
File "C:\Python33\lib\unittest\mock.py", line 1122, in get_original
"%s does not have the attribute %r" % (target, name)
AttributeError: <module 'RunFile' from 'C:\\Users\\jsalce\\Documents\\Testsuites\\IfStatements\\RunFile.py'> does not have the attribute 'print'
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
Thank you all in advance!
Generally speaking, for mock.patch.object, you want to patch something that you have an easy handle on -- e.g. a module or a class. Usually, you need to patch something that is one level above what you want to replace. For example if you want to patch the foo function in module bar, then you need mock.patch.object(bar, 'foo').
In your case, technically, print is a builtin, but you can patch it on the module where you're using it. This will add a RunFile.print "method" (which is actually a mock) that you can test assertions against. Apparently, since print doesn't actually exist on the module, we need to add create=True to tell mock to create RunFile.print since it doesn't already exist. With that in mind, I'd re-write the unittest as:
import RunFile
class TestMain(unittest.TestCase):
def test_main(self):
with mock.patch.object(RunFile, 'print', create=True) as mock_print:
RunFile.main()
expected_calls = [mock.call('Argh!') for _ in range(100)]
mock_print.assert_has_calls(expected_calls)
if __name__ == '__main__':
unittest.main()

'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