Ben
Ben

Reputation: 7124

Test behavior of code if optional module is not installed

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

Answers (1)

jsbueno
jsbueno

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

Related Questions