Как смоделировать функцию, вызываемую в функции внутри модуля с таким же именем? - PullRequest
0 голосов
/ 14 сентября 2018

Я пытаюсь использовать unittest.mock, но получаю сообщение об ошибке:

AttributeError: не имеет атрибута 'get_pledge_frequency'

У меня естьследующая структура файла:

pledges/views/
├── __init__.py
├── util.py
└── user_profile.py
pledges/tests/unit/profile
├── __init__.py
└── test_user.py

Внутри pledges/views/__init___.py У меня есть:

from .views import *
from .account import account
from .splash import splash
from .preferences import preferences
from .user_profile import user_profile

Внутри user_profile.py У меня есть функция с именем user_profile, которая вызывает функцию внутри util.pyназывается get_pledge_frequency следующим образом:

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

У меня есть тест внутри test_user.py следующим образом:

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

Я проверил другие вопросы, но ответы не охватывают, когда естьявляется ли функция модулем, и она импортируется в файл __init__.

Итак, есть ли способ решить эту проблему?Я в основном переименовал файл user_profile.py в profile, а затем я изменил тесты для ссылки на функцию внутри этого модуля, но мне интересно, возможно ли сохранить функцию и модуль с тем же именем.

1 Ответ

0 голосов
/ 16 сентября 2018

Оказывается, можно смоделировать функцию, вызываемую в функции внутри модуля с тем же именем. Маленькая оболочка вокруг unittest.mock.patch() может сделать это так:

Код:

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)

Для использования:

Вместо:

mock.patch("module.a.b")

вам нужно:

module_patch("module.a.b")

Как это работает?

Основная идея состоит в том, чтобы попытаться импортировать модуль, начатый с самого длинного возможного пути модуля к кратчайшему пути, и, если импорт успешен, использовать этот модуль в качестве исправленного объекта.

Тестовый код:

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
...