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() # Вызовет AttributeErrorAttributeError: 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:
with patch('module.ImportantClass', autospec=True) as MockClass:
instance = MockClass.return_value
instance.method.return_value = 'result'
# instance.non_existent_attribute = 1 # Вызовет AttributeError при autospec=TrueMockClass имитирует структуру ImportantClass.
Использование new_callable:
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:
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-объект файла с заданным содержимым.
Множественная подмена в одном тесте:
@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Оба вызова заменены, тест проходит.
Подмена асинхронного метода:
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.