Mock import failure - python

How do I make import pkg fail in moduleA.py? I can patch pkg to fail if something's imported from it, but not otherwise:
# test.py
import os
import moduleA
from unittest.mock import patch
from importlib import reload
#patch.dict('sys.modules', pkg=os)
def test_mock():
reload(moduleA)
# moduleA.py
import pkg # make this fail
from pkg import sum # this does fail
Live example

This is a bit more complicated. You have to make sure that reloading fails - this can be done by adding a class that implements find_spec. Second, you have to remove an already loaded package from sys.modules - otherwise this will be used on reload:
import sys
from importlib import reload
import pytest
import moduleA
class ImportRaiser:
def find_spec(self, fullname, path, target=None):
if fullname == 'pkg':
# we get here if the module is not loaded and not in sys.modules
raise ImportError()
sys.meta_path.insert(0, ImportRaiser())
def test_import_error():
if 'pkg' in sys.modules:
del sys.modules['pkg']
with pytest.raises(ImportError):
reload(moduleA)

Related

How to properly mock a function that instantiates a class variable?

I have something like this in my source file
# code.py
def some_func():
# doing some connections and stuff
return {'someKey': 'someVal'}
class ClassToTest:
var = some_func()
My test file looks like this... I am trying to mock some_func as I want to avoid creating connections.
# test_code.py
from src.code import ClassToTest
def mock_function():
return {"someOtherKey": "someOtherValue"}
class Test_Code(unittest.TestCase):
#mock.patch('src.code.some_func', new=mock_function)
def test_ClassToTest(self):
self.assertEqual(ClassToTest.var, {"someOtherKey": "someOtherValue"})
But this doesn't work. On the other hand if var is an instant variable mock works fine. I guess this is due to class variables getting initialized during imports. How do I properly mock some_func before var even gets initialized?
When you imported code.py, there is no active patch yet, so when ClassToTest.var was initialized, it used the original some_func. Only then would the patch for src.code.some_func would take effect which obviously is too late now.
Solution 1
What you can do is to patch some_func and then reload code.py so that it re-initializes the ClassToTest including its attribute var. Thus since we already have an active patch by the time we reload code.py, then ClassToTest.var would be set with the patched value.
But we can't do it if both the class and the patched function lives in the same file, so to make it testable move some_func to another file and then just import it.
src/code.py
from src.other import some_func
class ClassToTest:
var = some_func()
src/other.py
def some_func():
# doing some connections and stuff
return {'realKey': 'realValue'}
test_code.py
from importlib import reload
import sys
import unittest
from unittest import mock
from src.code import ClassToTest # This will always refer to the unpatched version
def mock_function():
return {"someOtherKey": "someOtherValue"}
class Test_Code(unittest.TestCase):
def test_real_first(self):
self.assertEqual(ClassToTest.var, {"realKey": "realValue"})
#mock.patch('src.other.some_func', new=mock_function)
def test_mock_then_reload(self):
# Option 1:
# import src
# reload(src.code)
# Option 2
reload(sys.modules['src.code'])
from src.code import ClassToTest # This will be the patched version
self.assertEqual(ClassToTest.var, {"someOtherKey": "someOtherValue"})
def test_real_last(self):
self.assertEqual(ClassToTest.var, {"realKey": "realValue"})
Output
$ pytest -q
... [100%]
3 passed in 0.04s
Solution 2
If you don't want the real some_func to be ever called during test, then just reloading isn't enough. What needs to be done is to never import the file containing ClassToTest nor any file that would import it indirectly. Only import it once an active patch for some_func is already established.
from importlib import reload
import sys
import unittest
from unittest import mock
# from src.code import ClassToTest # Remove this import!!!
def mock_function():
return {"someOtherKey": "someOtherValue"}
class Test_Code(unittest.TestCase):
#mock.patch('src.other.some_func', new=mock_function)
def test_mock_then_reload(self):
from src.code import ClassToTest # Move the import here once the patch has taken effect already
self.assertEqual(ClassToTest.var, {"someOtherKey": "someOtherValue"})

How to mock in a python unittest a library not installed locally?

I am trying to test using unittest a python 3.6 script that starts with (my_app.py):
import sys
from awsglue.utils import getResolvedOptions
args = getResolvedOptions(sys.argv, ['opt1', 'opt2', 'opt3'])
opt1 = args['opt1']
opt2 = args['opt2']
opt3 = args['opt3']
....
so in my test I did something like:
import unittest
import datetime
from mock import patch
import my_app
class TestMyApp(unittest.TestCase):
#patch('awsglue.utils.getResolvedOptions')
def test_mock_stubs(self, patch_opts):
patch_opts.return_value = {}
....
but the test soon fails at import my_app with:
ModuleNotFoundError: No module named 'awsglue'
as there is not awsglue locally installed. How can I test a module that import a not locally installed library and also mock it in my test?
You'll need to mock the imported module before the import for my_app happens. patch won't work here, because patch imports the module in order to patch it. And in this case, that import itself would cause an error.
To do this the simplest way is to trick python into thinking that awsglue is already imported. You can do that by putting your mock directly in the sys.modules dictionary. After this you can do the my_app import.
import unittest
import sys
from unittest import mock
class TestMyApp(unittest.TestCase):
def test_mock_stubs(self):
# mock the module
mocked_awsglue = mock.MagicMock()
# mock the import by hacking sys.modules
sys.modules['awsglue.utils'] = mocked_awsglue
mocked_awsglue.getResolvedOptions.return_value = {}
# move the import here to make sure that the mocks are setup before it's imported
import my_app
You can move the import hack part to a setup fixture method.
However I would suggest just installing the package. The import hack can be very difficult to maintain.

Pytest path of mock.patch() for third party package

Why the 1st case succeed. But the 2nd case failed AssertionError: Expected 'Jenkins' to have been called.
util.py
from jenkinsapi.jenkins import Jenkins
import os
class Util:
#staticmethod
def rm(filename):
os.remove(filename)
#staticmethod
def get_jenkins_instance():
Jenkins(
'host',
username='username',
password='password',
ssl_verify=False,
lazy=True)
test_util.py
import pytest
from util import Util
def test_util_remove(mocker):
m = mocker.patch('os.remove')
Util.rm('file')
m.assert_called()
def test_util_get_instance(mocker):
m = mocker.patch('jenkinsapi.jenkins.Jenkins')
Util.get_jenkins_instance()
m.assert_called()
Two files are in the same root folder.
Not very clear what's the differences between Python's import and from ... import ....
But if you use from ... import ..., the mock looks as following:
util.py
from jenkinsapi.jenkins import Jenkins # <-- difference A
class Util:
#staticmethod
def get_jenkins_instance():
Jenkins(
'host',
username='username',
password='password',
ssl_verify=False,
lazy=True)
test_util.py
import pytest
from util import Util
def test_util_get_instance(mocker):
m = mocker.patch('util.Jenkins') # <-- difference B
Util.get_jenkins_instance()
m.assert_called()
If you use import directly, the mock looks as following:
util.py
import jenkinsapi.jenkins # <-- difference A
class Util:
#staticmethod
def get_jenkins_instance():
jenkinsapi.jenkins.Jenkins(
'host',
username='username',
password='password',
ssl_verify=False,
lazy=True)
test_util.py
import pytest
from util import Util
def test_util_get_instance(mocker):
m = mocker.patch('jenkinsapi.jenkins.Jenkins') # <-- difference B
Util.get_jenkins_instance()
m.assert_called()
========== Edit (Aug 5, 2022) ==========
Here's the reason why patched like this.
a.py
-> Defines SomeClass
b.py
-> from a import SomeClass
-> some_function instantiates SomeClass
Now we want to test some_function but we want to mock out SomeClass using patch(). The problem is that when we import module b, which we will have to do then it imports SomeClass from module a. If we use patch() to mock out a.SomeClass then it will have no effect on our test; module b already has a reference to the real SomeClass and it looks like our patching had no effect.
The key is to patch out SomeClass where it is used (or where it is looked up ). In this case some_function will actually look up SomeClass in module b, where we have imported it.

Relative imports do not work from imported modules

I have a program that is laid out like the following:
test\test.py
test\modules\base.py
test\modules\blah.py
I need to load modules by name. Each module implements a class with the same methods, so I load them into a dictionary so that I can reference them as needed. I'm getting the follow error trying to do a relative import.
File "modules/blah.py", line 1, in <module>
from .base import BaseModule
ImportError: attempted relative import with no known parent package
Is there a way to use relative imports from code imported using importlib?
I'm using Python 3. The following is a simple example showing this error...
test\test.py:
#!/usr/bin/env python
import importlib
class Test():
def __init__(self):
spec = importlib.util.spec_from_file_location("blah", "modules/blah.py")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
def main():
t = Test()
if __name__ == '__main__':
main()
test\modules\base.py:
class BaseModule():
modulename = "base"
def __init__(self,name):
print("Initializing module %s" % (self.modulename))
test\modules\blah.py:
from .base import BaseModule
class BlahModule(BaseModule):
modulename = "blah"
Adding the following code should help:
import os
import sys
module_path = "modules/blah.py"
module_dir = os.path.dirname(module_path)
if module_dir not in sys.path:
sys.path.append(module_dir)
# do actual import of module here

re-import module-under-test to lose context

Many Python modules preserve an internal state without defining classes, e.g. logging maintains several loggers accessible via getLogger().
How do you test such a module? Using the standard unittest tools, I would like the various tests inside a TestCase class to re-import my module-under-test so that each time it loses its context. Can this be done?
import unittest
import sys
class Test(unittest.TestCase):
def tearDown(self):
try:
del sys.modules['logging']
except KeyError:
pass
def test_logging(self):
import logging
logging.foo=1
def test_logging2(self):
import logging
print(logging.foo)
if __name__ == '__main__':
unittest.sys.argv.insert(1,'--verbose')
unittest.main(argv = unittest.sys.argv)
% test.py Test.test_logging passes:
test_logging (__main__.Test) ... ok
but
% test.py Test.test_logging2 does not:
test_logging2 (__main__.Test) ... ERROR
since the internal state of logging has been reset.
This will reimport the module as new for you:
import sys
del sys.modules['my_module']
import my_module

Categories

Resources