3

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']
Ben
  • 6,986
  • 6
  • 44
  • 71
  • You can also mock the `__import__` magic for the specific test, see [this answer of mine](https://stackoverflow.com/a/51048604/2650249) for an example. – hoefling Mar 19 '20 at 09:58

1 Answers1

2

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)

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • yes - I had lost some editing, as I had to move back and forth tabs to reopen the prematurelly closed question. I think it is all there now. – jsbueno Mar 18 '20 at 19:33
  • Thank you, I had to screw around with it a bit to get it to work, but this was the right idea. In the end, I didn't have to reset the deleted packages. Python seemed to just reload them. Will edit my quesiton with my working solution – Ben Mar 18 '20 at 20:28