Unittest.mock.patch: примеры (PYTHON)

Использование unittest.mock.patch для изоляции модульных тестов в Python
Раздел: Тестирование, Мокирование
unittest.mock.patch(target, new, spec, create, spec_set, autospec, new_callable, kwargs): unittest.mock._patch

Основы unittest.mock.patch

Функция unittest.mock.patch временно заменяет целевой объект mock-объектом (или другим указанным объектом) на время выполнения теста. Это основной инструмент для подмены атрибутов, функций, классов или методов в модулях во время тестирования, позволяющий изолировать тестируемый код от внешних зависимостей.

Когда используется:

  • Для изоляции модульных тестов от внешних систем (базы данных, API, файловая система).
  • Для подмены случайных или зависимых от времени данных.
  • Для проверки вызовов функций с определенными аргументами.
  • Для тестирования исключительных ситуаций, которые сложно воспроизвести в реальных условиях.

Аргументы:

  • target (строка или объект): Путь к объекту для замены в формате 'module.ClassName.attribute'. Обязательный аргумент.
  • new (объект): Объект, который заменяет целевой. По умолчанию создается новый MagicMock.
  • spec (объект или список/кортеж): Спецификация для mock-объекта. Позволяет ограничить набор допустимых атрибутов.
  • create (bool): Если True, patch создаст атрибут, если он не существует. По умолчанию False.
  • spec_set (объект): Более строгая версия spec, запрещающая установку несуществующих атрибутов.
  • autospec (bool или объект): Автоматически создает спецификацию на основе заменяемого объекта. Может быть True или объектом для спецификации.
  • new_callable (класс): Класс, используемый для создания нового объекта. По умолчанию MagicMock.
  • **kwargs: Дополнительные аргументы для передачи конструктору mock-объекта.

Возвращаемые значения:

Функция возвращает объект patcher при вызове напрямую. При использовании в качестве декоратора или контекстного менеджера, она возвращает mock-объект (или указанный в new объект), который заменяет целевой объект.

Базовые примеры использования

Декоратор для замены функции:

from unittest.mock import patch
import module_to_test

@patch('module_to_test.external_api_call')
def test_function(mock_api):
    mock_api.return_value = 'mocked response'
    result = module_to_test.function_under_test()
    assert result == 'mocked response'
Тест проходит, external_api_call заменена mock-объектом.

Контекстный менеджер:

with patch('os.getcwd') as mock_getcwd:
    mock_getcwd.return_value = '/fake/dir'
    print(os.getcwd())
/fake/dir

Прямой вызов patcher:

patcher = patch('module.ClassName.method')
mock_method = patcher.start()
mock_method.return_value = 10
# выполнение теста
patcher.stop()
Метод ClassName.method заменен на mock.

Похожие функции в Python

patch.object

Используется для замены атрибута конкретного объекта. Удобно, когда объект уже импортирован или создан.

from unittest.mock import patch.object
obj = SomeClass()
with patch.object(obj, 'method', return_value=None) as mock_method:
    obj.method()

patch.dict

Временное изменение словаря (например, os.environ). Позволяет добавлять, удалять или изменять ключи на время теста.

with patch.dict('os.environ', {'NEW_KEY': 'value'}):
    print(os.environ.get('NEW_KEY'))

patch.multiple

Одновременная подмена нескольких объектов в одном контексте или декораторе.

@patch.multiple('module', func1=Mock(), func2=Mock())
def test(mocks):
    pass

Когда что использовать:

  • patch – для замены объектов по строковому пути.
  • patch.object – для замены атрибутов существующих объектов.
  • patch.dict – для временного изменения словарей.
  • patch.multiple – для массовой замены нескольких объектов.

Типичные ошибки

Неправильный путь к объекту:

# Ошибка: путь должен быть там, где объект используется, а не объявлен.
@patch('my_module.ExternalClass')
def test(self, mock_class):
    from other_module import some_function  # которая использует ExternalClass
    # patch не сработает, если some_function импортирует ExternalClass напрямую.
Mock не применяется, тест взаимодействует с реальным классом.

Использование create=True для существующего атрибута:

@patch('os.path.exists', create=True)  # create не нужен, т.к. exists существует
mock_exists.return_value = True
Работает, но может скрыть ошибки в коде.

Забыть про autospec ограничения:

@patch('module.SomeClass', autospec=True)
def test(mock_class):
    mock_class.non_existent_method()  # Вызовет AttributeError
AttributeError: Mock object has no attribute 'non_existent_method'

Изменения в последних версиях

В Python 3.8 добавлен аргумент spec в объекты Mock и MagicMock. В patch улучшена поддержка autospec для более точного соответствия спецификации.

В Python 3.10 улучшены сообщения об ошибках при неудачном сопоставлении вызовов mock-объектов.

В Python 3.11 добавлена возможность использования patch в асинхронных контекстах с улучшенной поддержкой асинхронных mock-объектов.

Расширенные примеры

Подмена класса с autospec:

Пример python
with patch('module.ImportantClass', autospec=True) as MockClass:
    instance = MockClass.return_value
    instance.method.return_value = 'result'
    # instance.non_existent_attribute = 1  # Вызовет AttributeError при autospec=True
MockClass имитирует структуру ImportantClass.

Использование new_callable:

Пример python
from unittest.mock import PropertyMock

class MyClass:
    @property
    def value(self):
        return 42

with patch('__main__.MyClass.value', new_callable=PropertyMock) as mock_prop:
    mock_prop.return_value = 100
    obj = MyClass()
    print(obj.value)
100

Подменяем встроенную функцию open:

Пример python
from unittest.mock import mock_open

with patch('builtins.open', mock_open(read_data='fake content')) as m:
    with open('file.txt') as f:
        content = f.read()
    m.assert_called_once_with('file.txt')
open подменена, возвращает mock-объект файла с заданным содержимым.

Множественная подмена в одном тесте:

Пример python
@patch('module.api_call', return_value=1)
@patch('module.other_call', return_value=2)
def test(mock_other, mock_api):
    # Порядок аргументов обратный порядку декораторов
    assert module.api_call() == 1
    assert module.other_call() == 2
Оба вызова заменены, тест проходит.

Подмена асинхронного метода:

Пример python
from unittest.mock import AsyncMock

@patch('module.async_func', new_callable=AsyncMock)
async def test(mock_async):
    mock_async.return_value = 'async result'
    result = await module.async_func()
    assert result == 'async result'
Асинхронная функция подменена и возвращает заданное значение.

Аналоги в других языках программирования

JavaScript (Jest):

Использует jest.spyOn и jest.mock для мокирования.

jest.spyOn(api, 'call').mockReturnValue('mocked');
const result = functionUnderTest();
expect(result).toBe('mocked');
Функция api.call подменена.

Java (Mockito):

Аннотации @Mock и @InjectMocks или метод Mockito.mock().

@Mock
SomeService serviceMock;

@Test
public void test() {
    when(serviceMock.doSomething()).thenReturn("mocked");
}
Метод doSomething возвращает "mocked".

Golang (gomock):

Генерация mock-интерфейсов через утилиту mockgen.

ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock := NewMockSomeInterface(ctrl)
mock.EXPECT().SomeMethod().Return(123)
SomeMethod интерфейса возвращает 123.

Kotlin (MockK):

Библиотека с поддержкой корутин и DSL.

val mock = mockk()
every { mock.someMethod() } returns 42
Метод someMethod возвращает 42.

Отличия от Python:

В статически типизированных языках (Java, Go) часто требуется генерация mock-классов на основе интерфейсов. В JavaScript и Kotlin подход ближе к динамическому подмену, как в Python.

питон unittest.mock.patch function comments

En
Unittest.mock.patch Patch object temporarily