Reputation: 69755
I am trying to use unittest.mock
, but I am getting an error:
AttributeError: does not have the attribute 'get_pledge_frequency'
I have the following file structure:
pledges/views/
├── __init__.py
├── util.py
└── user_profile.py
pledges/tests/unit/profile
├── __init__.py
└── test_user.py
Inside pledges/views/__init___.py
I have:
from .views import *
from .account import account
from .splash import splash
from .preferences import preferences
from .user_profile import user_profile
Inside, user_profile.py
I have a function called user_profile
which calls a function inside util.py
called get_pledge_frequency
as follows:
def user_profile(request, user_id):
# some logic
# !!!!!!!!!!!!!!!!
a, b = get_pledge_frequency(parameter) # this is the function I want to mock
# more logic
return some_value
I have a test inside test_user.py
as follows:
def test_name():
with mock.patch(
"pledges.views.user_profile.get_pledge_frequency"
) as get_pledge_frequency:
get_pledge_frequency.return_value = ([], [])
response = c.get(
reverse("pledges:user_profile", kwargs={"user_id": user.id})
) # this calls the function user_profile inside pledges.user_profile
# some asserts to verify functionality
I have checked other questions, but the answers do not cover when there is a function called as the module, and it is imported in the __init__
file.
So, is there any way to solve this problem? I have basically renamed the file user_profile.py
to profile
and then I have changed the tests to refer to the function inside this module, but I wonder if it is possible to keep the function and the module with the same name.
Upvotes: 3
Views: 1714
Reputation: 49794
It turns out it is possible to mock a function called in a function inside a module with the same name.
A small wrapper around unittest.mock.patch()
can make that happen like so:
from unittest import mock
import importlib
def module_patch(*args):
target = args[0]
components = target.split('.')
for i in range(len(components), 0, -1):
try:
# attempt to import the module
imported = importlib.import_module('.'.join(components[:i]))
# module was imported, let's use it in the patch
patch = mock.patch(*args)
patch.getter = lambda: imported
patch.attribute = '.'.join(components[i:])
return patch
except Exception as exc:
pass
# did not find a module, just return the default mock
return mock.patch(*args)
Instead of:
mock.patch("module.a.b")
you need:
module_patch("module.a.b")
Basic idea is to attempt module imports started with the longest possible module path towards the shortest path, and if an import is successful use that module as the patched object.
import module
print('module.a(): ', module.a())
print('module.b(): ', module.b())
print('--')
with module_patch("module.a.b") as module_a_b:
module_a_b.return_value = 'new_b'
print('module.a(): ', module.a())
print('module.b(): ', module.b())
try:
mock.patch("module.a.b").__enter__()
assert False, "Attribute error was not raised, test case is broken"
except AttributeError:
pass
module
# __init__.py
from .a import a
from .a import b
# a.py
def a():
return b()
def b():
return 'b'
module.a(): b
module.b(): b
--
module.a(): new_b
module.b(): b
Upvotes: 7