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
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()