How to unit test the code without having the depending modules available at all?
This happens often when you work with API libraries, which are provided as a part of an installed product. And worst of all, the development kits are only provided for the certain OS - but not for yours.
Unit testing and mocking is less painful when the API is based on objects and classes. If the depending module is static and has a state, unit tests end up being a bit of a mess.
Unit testing without the depending module
Assuming the following is the code you've created. You don't have the missing_module implementation around for the unit tests.
mocked.py
_____________________________________________
import missing_module
class ClassWithDependency:
def call_dependency(self):
try:
return missing_module.things.do()
except missing_module.Error as ne:
raise RuntimeError()
_____________________________________________
Writing the unit tests without the depending module
The code above is simple, but writing unit tests is slightly challenging for several reasons:
- There is no missing_module to import in test unit.
- Mocked methods of missing_module must be implemented one by one without the original module. If you had the missing_module in hand, you could just partially mock some parts of the module.
- If the missing_module is a static module, the mock must be 'reset' manually after each test.
- When the missing_module has a custom Exceptions as well, things get especially awful. This is because the 'try-catch' of the Python uses isinstance() and your mock's parent class is not BaseException, which the Python standard assumes. And we can't use "spec=...." to fix that issue, as we don't have the missing_module in hand.
The solution for unit testing the code is to do some tricks to work around the issue of lack of missing_module. Comments inline.
test_mocked.py
_____________________________________________
import unittest
import mock
# We must mock the import, as we don't have the module available
import sys
sys.modules['missing_module'] = mock.MagicMock()
import mocked
class TestElementCases(unittest.TestCase):
# A convenience hook for tests
missing = sys.modules['missing_module']
def tearDown(self):
# It's a static module, so these must be cleared
# on every test
self.missing.things.do.side_effect = None
self.missing.things.do.return_value = None
def test_call_dependecy_successful(self):
# Return value for missing_module functions
# through the hook. Any other mock methods and
# variables can be used through it as well
# (eg. 'called')
self.missing.things.do.return_value = "Bar"
dependency = mocked.ClassWithDependency()
# Success
self.assertEqual(dependency.call_dependency(), "Bar")
# Patch must be set as we have to work around the isinstance().
# Default Exception satisfies the lookup.
# Lambda and new_callable returns
# the implementation instead of the one which we don't have
@mock.patch('mocked.missing_module.Error',
new_callable=lambda: Exception)
def test_call_dependecy_exception(self,stub_excption):
self.missing.things.do.side_effect = stub_excption()
dependency = mocked.ClassWithDependency()
# The custom exception was thrown Succesfully
# (the code converts that to RTE), but
# that's just a nuance
with self.assertRaises(RuntimeError) as rer:
dependency.call_dependency()
_____________________________________________
That's it. Now every line of mocked.py is getting covered 100% in unit tests without the missing_module.
You're good to go.
No comments:
Post a Comment