Home > Net >  ModuleNotFoundError in unittest when mocking class method
ModuleNotFoundError in unittest when mocking class method

Time:10-21

I want to test a method of a class. There I want to mock a method that is in another class. But I always get the error below.

Error

Ran 1 test in 0.005s

FAILED (errors=1)

Error
Traceback (most recent call last):
  File "D:\dev\test_unittest\lib\site-packages\mock\mock.py", line 1343, in patched
    with self.decoration_helper(patched,
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.3568.0_x64__qbz5n2kfra8p0\lib\contextlib.py", line 119, in __enter__
    return next(self.gen)
  File "D:\dev\test_unittest\lib\site-packages\mock\mock.py", line 1325, in decoration_helper
    arg = exit_stack.enter_context(patching)
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.3568.0_x64__qbz5n2kfra8p0\lib\contextlib.py", line 448, in enter_context
    result = _cm_type.__enter__(cm)
  File "D:\dev\test_unittest\lib\site-packages\mock\mock.py", line 1398, in __enter__
    self.target = self.getter()
  File "D:\dev\test_unittest\lib\site-packages\mock\mock.py", line 1573, in <lambda>
    getter = lambda: _importer(target)
  File "D:\dev\test_unittest\lib\site-packages\mock\mock.py", line 1245, in _importer
    thing = __import__(import_path)
ModuleNotFoundError: No module named 'foo'

Folder Structure

enter image description here

foo.py

from parentfolder.handler import Handler

class Foo:
    def __init__(self):
        self.handler = Handler()

    def verify_client(self):
        client = self.handler.get_value('client')
        return client == 'client'

if __name__ == '__main__':
    foo = Foo()
    re = foo.verify_client()
    print(re)

handler.py

class Handler:
    def get_value(self, value):
        return value

test.py

import unittest
import mock
from parentfolder.foo import Foo


class testFoo(unittest.TestCase):
    @mock.patch('foo.Foo.get_value', return_value='client')
    def test_verify_client(self):
        foo = Foo()
        result = foo.verify_client()
        self.assertTrue(result)


if __name__ == "__main__":
    unittest.main()

CodePudding user response:

There are a number of problems with your unit test code.

Invalid import path

This is what is causing the error you are seeing. If you look at the documentation of unittest.mock.patch, you'll see that the target string must be in a form that is importable from the environment you are calling patch from, i.e. the one where you execute your unit test in.

No module named foo can be directly imported from that environment because that module is located in the parentfolder package. So the package name must be included in the module path like this: patch("parentfolder.foo.[...]")

The same reason you imported the Foo class at the top of your test module by providing the parentfolder in the import path.

Non-existent attribute

Even if you fix that import path, the rest of that target string is still wrong because your Foo class in the foo module does not have an attribute named get_value. That is a method of your Handler class. And if you want to patch that method, you need to write your target string like this: patch("parentfolder.foo.Handler.get_value")

Notice that I don't need to write the path to the handler module because the Handler class is imported into the foo module, meaning it will be in the namespace of foo, when patch is called. It would be equivalent in this case to write it like this: patch("parentfolder.handler.Handler.get_value")

Missing test method parameter

Using patch as a decorator for your test method means you must define it with an additional parameter that takes the mock created by patch as the argument or you must provide the new argument to patch. The typical setup looks like this:

@patch("parentfolder.foo.Handler.get_value")
def test_verify_client(self, mock_get_value):
    ...

Why not use unittest.mock?

This is not that big of a deal. It is just strange, why you would install a separate package, when mock has been part of the standard library's unittest package since Python 3.3.

Full working example

All in all, this is how I would rewrite your test module:

from unittest import TestCase, main
from unittest.mock import MagicMock, patch

from parentfolder.foo import Foo


class FooTestCase(TestCase):
    @patch("parentfolder.foo.Handler.get_value")
    def test_verify_client(self, mock_get_value: MagicMock) -> None:
        mock_get_value.return_value = "client"
        foo = Foo()
        result = foo.verify_client()
        self.assertTrue(result)


if __name__ == "__main__":
    main()

Alternatively, I like to import the module under testing itself and then just patch its namespace directly (without additional imports) using patch.object, which is possible because a module (like literally everything else in Python) is an object and its namespace represents the attributes of that object. It is a matter of personal preference I suppose:

from unittest import TestCase, main
from unittest.mock import MagicMock, patch

from parentfolder import foo as foo_module


class FooTestCase(TestCase):
    @patch.object(foo_module.Handler, "get_value")
    def test_verify_client(self, mock_get_value: MagicMock) -> None:
        mock_get_value.return_value = "client"
        foo = foo_module.Foo()
        result = foo.verify_client()
        self.assertTrue(result)


if __name__ == "__main__":
    main()
  • Related