I have a test_a.py, a.py and b.py in 3 different directories in my test environment.
b.py
class SBD():
def __init__(self):
print("SBD created (In B)")
a.py
import b
from b import *
print("In module A")
def fun1():
a=SBD()
print("SBD created(In A)")
test_a.py
import unittest
import sys
from unittest.mock import Mock,patch,MagicMock
sys.path.append("../abc/")
import b as c
sys.modules['b'] = MagicMock(spec=c)
sys.path.append("../xyz/")
import a
class TestStringMethods(unittest.TestCase):
def test_isupper(self):
a.fun1()
if __name__ == '__main__':
unittest.main()
In a real situation, b.py will have multiple classes and I wanted to mock all of them, so I tried mocking the module b with the same specifications. But when i run the test_a.py it gives me an error saying "SBD" is not defined. What am I doing wrong here?
A MagicMock
instance does not provide the same information to the import machinery as a module would. Even with a spec
, there is no actual SDB
attribute defined on the mock, so from b import *
won't find it.
The from *
import machinery tries two different things:
__all__
; if defined it must be a list of strings of names to import.__all__
is not defined, the keys of the __dict__
attribute are taken, filtering out names that start with an underscore.Because your b
module has no __all__
list defined, the __dict__
keys are taken instead. For a MagicMock
instance specced against a module, the __dict__
attribute only consists of names with _
underscores and the mock_calls
attribute. from b import *
only imports mock_calls
:
>>> import a as c
>>> module_mock = MagicMock(spec=c)
>>> [n for n in module_mock.__dict__ if n[:1] != '_']
['method_calls']
I would strongly advice against mocking the whole module; doing this would require that you postpone importing a
, and is fragile. The patch is permanent (is not undone automatically when tests end) and won't support repeated runs of the test or running tests in random order.
But if you had to make this work, you could add a __all__
attribute to the mock first:
sys.modules['b'] = MagicMock(spec=c, __all__=[n for n in c.__dict__ if n[:1] != '_'])
Personally, I'd a) avoid using from module import *
syntax altogether. If I could not prevent this from being used anyway, the next step would be to apply patches to a
after importing, looping over the b
module to obtain specced replacements:
# avoid manipulating sys.path if at all possible. Move that to a PYTHONPATH
# variable or install the modules properly.
import unittest
from unittest import mock
import a
import b
class TestStringMethods(unittest.TestCase):
def setUp(self):
# mock everything `from b import *` would import
b_names = getattr(b, '__all__', None)
if b_names is None:
b_names = [n for n in b.__dict__ if n[:1] != '_']
self.b_mocks = {}
for name in b_names:
orig = getattr(b, name, None)
if orig is None:
continue
self.b_mocks[name] = mock.patch.object(a, name, spec=orig)
self.b_mocks[name].start()
self.addCleanup(self.b_mocks[name].stop)
def test_isupper(self):
a.fun1()
This leaves sys.modules['b']
untouched, and processes the exact same names that from *
would load. The patches are removed again after the test ends.
The above test outputs:
$ python test_a.py
In module A
SBD created(In A)
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK