Reputation: 7124
I have a software package, packageA
, which has an optional dependency on packageB
. If packageB
is installed, then the user has access to some additional functionality. But, not having it installed does not limit the use of packageA
.
I want to write a unit (or integration?) test that checks packageA
is still usable if packageB
is not installed... that is, it won't unexpectedly throw an ImportError
for packagB
The test I have is written like this:
from unittest import mock
import pytest
def test_no_err_if_packageb_not_installed():
import sys
sys.modules['packageB'] = mock.MagicMock()
try:
from packageA import file_that_uses_packageb # noqa: F401
except NameError:
pytest.fail("packagB dependency found at root level")
And this works, if it's called in isolation. But when it's called as part of the testing suite, this test will always pass. This is because sys.modules
will already have been populated with the import modules by previous tests. I've tried to del
the package from sys.modules
first, but this doesn't work
What is the correct way to write this test?
Edit with working solution
from unittest import mock
import pytest
def test_no_err_if_packageb_not_installed():
import sys
# clear cached modules
mods = list(sys.modules.keys())
for mod in mods:
if 'packageA' in mod or 'packageB' in mod:
del sys.modules[mod]
sys.modules['packageB'] = mock.MagicMock()
try:
from packageA import file_that_uses_packageb # noqa: F401
except NameError:
pytest.fail("packagB dependency found at root level")
del sys.modules['packageB']
Upvotes: 3
Views: 445
Reputation: 110271
Hacking with sys.modules
for this is the way to go. The problem is that you are reseting the import of packageB
- but actually, to make this test, you have also to reset the import of file_that_uses_packageb
.
In your test setup try del sys.modules["packageB"], sys.modules["packageA.file_that_uses_packageB"]
and you should be all set.
Don't forget to reset those again at the end of your test, or other tests actually using packageB
will fail.
(the answer that was temptativelly marked as "duplicate" answer does not touch the same points -the answer talks about using imp.reload
, which resets and re-runs a module, ok - but won't allow you to replace a modules code with mocks if needed)
Upvotes: 2