unittest.mock — Библиотека насмешливых объектов

Added in version 3.3.

Источник: Lib/unittest/mock.py


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

unittest.mock предоставляет основной класс Mock, избавляющий от необходимости создавать множество заглушек для всего набора тестов. После выполнения действия вы можете сделать утверждения о том, какие методы/атрибуты были использованы и с какими аргументами они были вызваны. Вы также можете указывать возвращаемые значения и устанавливать необходимые атрибуты обычным способом.

Кроме того, mock предоставляет декоратор patch() для изменения атрибутов уровня модуля и класса в рамках теста, а также sentinel для создания уникальных объектов. Примеры использования Mock, MagicMock и patch() см. в разделе quick guide.

Mock разработан для использования с unittest и основан на шаблоне „action -> assertion“, а не „record -> replay“, используемом во многих mocking-фреймворках.

Существует бэкпорт unittest.mock для более ранних версий Python, доступный как mock на PyPI.

Краткое руководство

Объекты Mock и MagicMock создают все атрибуты и методы по мере обращения к ним и хранят сведения о том, как они были использованы. Вы можете настроить их, указать возвращаемые значения или ограничить доступные атрибуты, а затем сделать утверждения о том, как они были использованы:

>>> from unittest.mock import MagicMock
>>> thing = ProductionClass()
>>> thing.method = MagicMock(return_value=3)
>>> thing.method(3, 4, 5, key='value')
3
>>> thing.method.assert_called_with(3, 4, 5, key='value')

side_effect позволяет выполнять побочные эффекты, в том числе вызывать исключение при вызове имитатора:

>>> from unittest.mock import Mock
>>> mock = Mock(side_effect=KeyError('foo'))
>>> mock()
Traceback (most recent call last):
 ...
KeyError: 'foo'
>>> values = {'a': 1, 'b': 2, 'c': 3}
>>> def side_effect(arg):
...     return values[arg]
...
>>> mock.side_effect = side_effect
>>> mock('a'), mock('b'), mock('c')
(1, 2, 3)
>>> mock.side_effect = [5, 4, 3, 2, 1]
>>> mock(), mock(), mock()
(5, 4, 3)

У Mock есть множество других способов, с помощью которых вы можете настраивать его и управлять его поведением. Например, аргумент spec настраивает mock на получение спецификации от другого объекта. Попытка получить доступ к атрибутам или методам макета, которые не существуют в спецификации, завершится ошибкой AttributeError.

Декоратор patch() / менеджер контекста позволяет легко подражать классам или объектам в тестируемом модуле. Указанный вами объект будет заменен имитатором (или другим объектом) во время тестирования и восстановлен по его завершении:

>>> from unittest.mock import patch
>>> @patch('module.ClassName2')
... @patch('module.ClassName1')
... def test(MockClass1, MockClass2):
...     module.ClassName1()
...     module.ClassName2()
...     assert MockClass1 is module.ClassName1
...     assert MockClass2 is module.ClassName2
...     assert MockClass1.called
...     assert MockClass2.called
...
>>> test()

Примечание

При вложении патч-декораторов имитаторы передаются в декорированную функцию в том же порядке, в котором они применялись (обычный Python порядок применения декораторов). Это означает, что они передаются снизу вверх, поэтому в приведенном выше примере первым передается имитатор для module.ClassName1.

При использовании patch() важно, чтобы вы сопрягали объекты в том пространстве имен, в котором они ищутся. Обычно это несложно, но для краткого руководства прочтите where to patch.

Также как декоратор patch() может использоваться в качестве менеджера контекста в операторе with:

>>> with patch.object(ProductionClass, 'method', return_value=None) as mock_method:
...     thing = ProductionClass()
...     thing.method(1, 2, 3)
...
>>> mock_method.assert_called_once_with(1, 2, 3)

Существует также patch.dict() для установки значений в словарь непосредственно во время выполнения области видимости и восстановления словаря в исходное состояние по окончании теста:

>>> foo = {'key': 'value'}
>>> original = foo.copy()
>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
...     assert foo == {'newkey': 'newvalue'}
...
>>> assert foo == original

Mock поддерживает макирование классов Python magic methods. Самый простой способ использовать магические методы - это класс MagicMock. Он позволяет делать такие вещи, как:

>>> mock = MagicMock()
>>> mock.__str__.return_value = 'foobarbaz'
>>> str(mock)
'foobarbaz'
>>> mock.__str__.assert_called_with()

Mock позволяет назначать функции (или другие экземпляры Mock) на магические методы, и они будут вызываться соответствующим образом. Класс MagicMock - это просто вариант Mock, в котором все магические методы уже созданы для вас (ну, во всяком случае, все полезные).

Ниже приведен пример использования магических методов с обычным классом Mock:

>>> mock = Mock()
>>> mock.__str__ = Mock(return_value='wheeeeee')
>>> str(mock)
'wheeeeee'

Чтобы убедиться, что подражаемые объекты в ваших тестах имеют тот же api, что и объекты, которые они заменяют, вы можете использовать auto-speccing. Автоспецификация может быть выполнена с помощью аргумента autospec в патче или функции create_autospec(). Автоспецификация создает объекты-макеты, которые имеют те же атрибуты и методы, что и объекты, которые они заменяют, а все функции и методы (включая конструкторы) имеют ту же сигнатуру вызова, что и реальный объект.

Это гарантирует, что при неправильном использовании ваши имитаторы будут работать так же, как и ваш рабочий код:

>>> from unittest.mock import create_autospec
>>> def function(a, b, c):
...     pass
...
>>> mock_function = create_autospec(function, return_value='fishy')
>>> mock_function(1, 2, 3)
'fishy'
>>> mock_function.assert_called_once_with(1, 2, 3)
>>> mock_function('wrong arguments')
Traceback (most recent call last):
 ...
TypeError: missing a required argument: 'b'

create_autospec() также может использоваться для классов, где он копирует сигнатуру метода __init__, и для вызываемых объектов, где он копирует сигнатуру метода __call__.

Шуточный класс

Mock - это гибкий объект имитации, предназначенный для замены использования заглушек и тестовых дублей в вашем коде. Макеты являются вызываемыми и создают атрибуты в качестве новых макетов, когда вы обращаетесь к ним [1]. Обращение к одному и тому же атрибуту всегда будет возвращать один и тот же mock. Моки записывают, как вы их используете, позволяя вам делать утверждения о том, что ваш код сделал с ними.

MagicMock - это подкласс Mock, в котором все магические методы уже созданы и готовы к использованию. Существуют также невызываемые варианты, полезные, когда вы подражаете объектам, которые не являются вызываемыми: NonCallableMock и NonCallableMagicMock.

Декораторы patch() позволяют легко временно заменить классы в определенном модуле объектом Mock. По умолчанию patch() создаст для вас MagicMock. Вы можете указать альтернативный класс Mock, используя аргумент new_callable в patch().

class unittest.mock.Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)

Создайте новый объект Mock. Mock принимает несколько необязательных аргументов, которые определяют поведение объекта Mock:

  • spec: Это может быть либо список строк, либо существующий объект (класс или экземпляр), который выступает в качестве спецификации для объекта-макета. Если вы передаете объект, то список строк формируется путем вызова dir на объекте (исключая неподдерживаемые магические атрибуты и методы). Доступ к любому атрибуту, не входящему в этот список, вызовет ошибку AttributeError.

    Если spec является объектом (а не списком строк), то __class__ возвращает класс объекта spec. Это позволяет мокам проходить тесты isinstance().

  • spec_set: Более строгий вариант spec. При использовании этого варианта попытка установить или получить атрибут на имитаторе, который не находится на объекте, переданном в качестве spec_set, вызовет ошибку AttributeError.

  • side_effect: Функция, которая будет вызываться при каждом вызове Mock. См. атрибут side_effect. Пригодится для создания исключений или динамического изменения возвращаемых значений. Функция вызывается с теми же аргументами, что и mock, и если она не возвращает DEFAULT, то в качестве возвращаемого значения используется значение этой функции.

    В качестве альтернативы side_effect может быть классом или экземпляром исключения. В этом случае исключение будет вызвано при вызове имитатора.

    Если side_effect - это итерируемая переменная, то каждый вызов mock будет возвращать следующее значение из итерируемой переменной.

    Эффект side_effect можно снять, установив его в None.

  • return_value: Значение, возвращаемое при вызове макета. По умолчанию это новый Mock (создается при первом обращении). См. атрибут return_value.

  • unsafe: По умолчанию доступ к любому атрибуту, имя которого начинается с assert, assret, asert, aseert или assrt, вызовет ошибку AttributeError. Передача unsafe=True разрешит доступ к этим атрибутам.

    Added in version 3.5.

  • wraps: Элемент для обертывания объекта mock. Если wraps не None, то вызов Mock будет передавать вызов обернутому объекту (возвращая реальный результат). Обращение к атрибутам объекта mock вернет объект Mock, который обертывает соответствующий атрибут обернутого объекта (поэтому попытка получить доступ к несуществующему атрибуту вызовет ошибку AttributeError).

    Если в mock явно задано return_value, то вызовы не передаются обернутому объекту, а вместо них возвращается return_value.

  • имя: Если у имитатора есть имя, то оно будет использоваться в репрезентации имитатора. Это может быть полезно для отладки. Имя передается дочерним мейкам.

Макеты также можно вызывать с произвольными аргументами в виде ключевых слов. Они будут использоваться для установки атрибутов макета после его создания. Подробности см. в методе configure_mock().

assert_called()

Убедитесь, что подражатель был вызван хотя бы один раз.

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called()

Added in version 3.6.

assert_called_once()

Утверждайте, что этот имитатор был вызван ровно один раз.

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_once()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_once()
Traceback (most recent call last):
...
AssertionError: Expected 'method' to have been called once. Called 2 times.
Calls: [call(), call()].

Added in version 3.6.

assert_called_with(*args, **kwargs)

Этот метод - удобный способ утверждать, что последний вызов был выполнен определенным образом:

>>> mock = Mock()
>>> mock.method(1, 2, 3, test='wow')
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_with(1, 2, 3, test='wow')
assert_called_once_with(*args, **kwargs)

Убедитесь, что mock был вызван ровно один раз, и этот вызов был с указанными аргументами.

>>> mock = Mock(return_value=None)
>>> mock('foo', bar='baz')
>>> mock.assert_called_once_with('foo', bar='baz')
>>> mock('other', bar='values')
>>> mock.assert_called_once_with('other', bar='values')
Traceback (most recent call last):
  ...
AssertionError: Expected 'mock' to be called once. Called 2 times.
Calls: [call('foo', bar='baz'), call('other', bar='values')].
assert_any_call(*args, **kwargs)

Утверждает, что mock был вызван с указанными аргументами.

Утверждение проходит, если mock всегда вызывался, в отличие от assert_called_with() и assert_called_once_with(), которые проходят, только если вызов был последним, а в случае assert_called_once_with() он должен быть единственным.

>>> mock = Mock(return_value=None)
>>> mock(1, 2, arg='thing')
>>> mock('some', 'thing', 'else')
>>> mock.assert_any_call(1, 2, arg='thing')
assert_has_calls(calls, any_order=False)

Утверждает, что мейк был вызван с указанными вызовами. Список mock_calls проверяется на наличие вызовов.

Если any_order равно false, то вызовы должны быть последовательными. Могут быть дополнительные вызовы до или после указанных вызовов.

Если any_order равно true, то вызовы могут располагаться в любом порядке, но все они должны появляться в mock_calls.

>>> mock = Mock(return_value=None)
>>> mock(1)
>>> mock(2)
>>> mock(3)
>>> mock(4)
>>> calls = [call(2), call(3)]
>>> mock.assert_has_calls(calls)
>>> calls = [call(4), call(2), call(3)]
>>> mock.assert_has_calls(calls, any_order=True)
assert_not_called()

Утверждайте, что насмешка не была вызвана.

>>> m = Mock()
>>> m.hello.assert_not_called()
>>> obj = m.hello()
>>> m.hello.assert_not_called()
Traceback (most recent call last):
  ...
AssertionError: Expected 'hello' to not have been called. Called 1 times.
Calls: [call()].

Added in version 3.5.

reset_mock(*, return_value=False, side_effect=False)

Метод reset_mock сбрасывает все атрибуты вызова для объекта-макета:

>>> mock = Mock(return_value=None)
>>> mock('hello')
>>> mock.called
True
>>> mock.reset_mock()
>>> mock.called
False

Изменено в версии 3.6: В функцию reset_mock добавлены два аргумента, относящиеся только к ключевым словам.

Это может быть полезно, если вы хотите сделать серию утверждений, в которых повторно используется один и тот же объект. Обратите внимание, что reset_mock() не очищает return_value, side_effect или любые дочерние атрибуты, которые вы задали с помощью обычного присваивания по умолчанию. Если вы хотите сбросить return_value или side_effect, то передайте соответствующий параметр как True. Дочерние имитаторы и имитатор возвращаемого значения (если таковые имеются) также сбрасываются.

Примечание

возвращаемое_значение и боковой_эффект являются аргументами только для ключевого слова.

mock_add_spec(spec, spec_set=False)

Добавляет спецификацию к имитатору. spec может быть либо объектом, либо списком строк. Только атрибуты из spec могут быть получены как атрибуты из макета.

Если spec_set равно true, то можно установить только атрибуты спецификации.

attach_mock(mock, attribute)

Прикрепите имитатор в качестве атрибута данного, заменив его имя и родителя. Обращения к прикрепленному макету будут записываться в атрибуты method_calls и mock_calls этого макета.

configure_mock(**kwargs)

Устанавливает атрибуты макета с помощью аргументов ключевых слов.

Атрибуты, возвращаемые значения и побочные эффекты могут быть заданы в дочерних моках с помощью стандартной точечной нотации и распаковки словаря в вызове метода:

>>> mock = Mock()
>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock.configure_mock(**attrs)
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

Того же самого можно добиться при вызове конструктора в моках:

>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock = Mock(some_attribute='eggs', **attrs)
>>> mock.some_attribute
'eggs'
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

configure_mock() существует для того, чтобы упростить настройку после создания имитатора.

__dir__()

Объекты Mock ограничивают результаты dir(some_mock) до полезных результатов. Для макетов с spec это включает все разрешенные атрибуты для макета.

О том, что делает эта фильтрация и как ее отключить, читайте в разделе FILTER_DIR.

_get_child_mock(**kw)

Создайте дочерние модели для атрибутов и возвращаемого значения. По умолчанию дочерние имитаторы будут того же типа, что и родительский. Подклассы Mock могут захотеть переопределить этот параметр, чтобы настроить способ создания дочерних имитаторов.

Для невызываемых mocks будет использоваться вызываемый вариант (а не любой пользовательский подкласс).

called

Булево число, показывающее, был ли вызван объект mock:

>>> mock = Mock(return_value=None)
>>> mock.called
False
>>> mock()
>>> mock.called
True
call_count

Целое число, показывающее, сколько раз был вызван объект mock:

>>> mock = Mock(return_value=None)
>>> mock.call_count
0
>>> mock()
>>> mock()
>>> mock.call_count
2
return_value

Установите этот параметр для настройки значения, возвращаемого при вызове mock:

>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'

Возвращаемое значение по умолчанию - объект mock, и вы можете настроить его обычным способом:

>>> mock = Mock()
>>> mock.return_value.attribute = sentinel.Attribute
>>> mock.return_value()
<Mock name='mock()()' id='...'>
>>> mock.return_value.assert_called_with()

return_value также может быть задан в конструкторе:

>>> mock = Mock(return_value=3)
>>> mock.return_value
3
>>> mock()
3
side_effect

Это может быть либо функция, которая будет вызываться при вызове mock, либо итерабельность, либо исключение (класс или экземпляр), которое должно быть поднято.

Если вы передадите функцию, то она будет вызвана с теми же аргументами, что и mock, и если функция не вернет синглтон DEFAULT, то вызов mock вернет все, что вернет функция. Если функция вернет DEFAULT, то mock вернет свое обычное значение (из return_value).

Если вы передаете iterable, он используется для получения итератора, который должен выдавать значение при каждом вызове. Этим значением может быть либо экземпляр исключения, который должен быть поднят, либо значение, которое должно быть возвращено из вызова mock (обработка:data:DEFAULT идентична случаю с функцией).

Пример имитатора, который вызывает исключение (для проверки обработки исключений в API):

>>> mock = Mock()
>>> mock.side_effect = Exception('Boom!')
>>> mock()
Traceback (most recent call last):
  ...
Exception: Boom!

Использование side_effect для возврата последовательности значений:

>>> mock = Mock()
>>> mock.side_effect = [3, 2, 1]
>>> mock(), mock(), mock()
(3, 2, 1)

Использование вызываемого объекта:

>>> mock = Mock(return_value=3)
>>> def side_effect(*args, **kwargs):
...     return DEFAULT
...
>>> mock.side_effect = side_effect
>>> mock()
3

side_effect может быть задан в конструкторе. Вот пример, который добавляет единицу к значению, с которым вызывается mock, и возвращает ее:

>>> side_effect = lambda value: value + 1
>>> mock = Mock(side_effect=side_effect)
>>> mock(3)
4
>>> mock(-8)
-7

Установка side_effect в None очищает его:

>>> m = Mock(side_effect=KeyError, return_value=3)
>>> m()
Traceback (most recent call last):
 ...
KeyError
>>> m.side_effect = None
>>> m()
3
call_args

Это либо None (если mock не был вызван), либо аргументы, с которыми mock был вызван в последний раз. Это будет кортеж: первый член, к которому также можно получить доступ через свойство args, - это все упорядоченные аргументы, с которыми был вызван mock (или пустой кортеж), а второй член, к которому также можно получить доступ через свойство kwargs, - это все аргументы ключевого слова (или пустой словарь).

>>> mock = Mock(return_value=None)
>>> print(mock.call_args)
None
>>> mock()
>>> mock.call_args
call()
>>> mock.call_args == ()
True
>>> mock(3, 4)
>>> mock.call_args
call(3, 4)
>>> mock.call_args == ((3, 4),)
True
>>> mock.call_args.args
(3, 4)
>>> mock.call_args.kwargs
{}
>>> mock(3, 4, 5, key='fish', next='w00t!')
>>> mock.call_args
call(3, 4, 5, key='fish', next='w00t!')
>>> mock.call_args.args
(3, 4, 5)
>>> mock.call_args.kwargs
{'key': 'fish', 'next': 'w00t!'}

call_args, а также члены списков call_args_list, method_calls и mock_calls являются объектами call. Это кортежи, поэтому их можно распаковать, чтобы получить отдельные аргументы и сделать более сложные утверждения. См. calls as tuples.

Изменено в версии 3.8: Добавлены свойства args и kwargs.

call_args_list

Это список всех последовательных вызовов объекта mock (поэтому длина списка равна количеству вызовов). Пока не было сделано ни одного вызова, это пустой список. Объект call можно использовать для удобного построения списков вызовов для сравнения с call_args_list.

>>> mock = Mock(return_value=None)
>>> mock()
>>> mock(3, 4)
>>> mock(key='fish', next='w00t!')
>>> mock.call_args_list
[call(), call(3, 4), call(key='fish', next='w00t!')]
>>> expected = [(), ((3, 4),), ({'key': 'fish', 'next': 'w00t!'},)]
>>> mock.call_args_list == expected
True

Членами call_args_list являются объекты call. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. calls as tuples.

method_calls

Помимо отслеживания вызовов самих себя, имитаторы также отслеживают вызовы методов и атрибутов, а также своих методов и атрибутов:

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.property.method.attribute()
<Mock name='mock.property.method.attribute()' id='...'>
>>> mock.method_calls
[call.method(), call.property.method.attribute()]

Членами method_calls являются объекты call. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. calls as tuples.

mock_calls

mock_calls записывает все обращения к объекту-макету, его методам, магическим методам и макетам возвращаемых значений.

>>> mock = MagicMock()
>>> result = mock(1, 2, 3)
>>> mock.first(a=3)
<MagicMock name='mock.first()' id='...'>
>>> mock.second()
<MagicMock name='mock.second()' id='...'>
>>> int(mock)
1
>>> result(1)
<MagicMock name='mock()()' id='...'>
>>> expected = [call(1, 2, 3), call.first(a=3), call.second(),
... call.__int__(), call()(1)]
>>> mock.mock_calls == expected
True

Членами mock_calls являются объекты call. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. calls as tuples.

Примечание

Способ записи mock_calls означает, что при выполнении вложенных вызовов параметры вызовов-предшественников не записываются и поэтому всегда будут равны:

>>> mock = MagicMock()
>>> mock.top(a=3).bottom()
<MagicMock name='mock.top().bottom()' id='...'>
>>> mock.mock_calls
[call.top(a=3), call.top().bottom()]
>>> mock.mock_calls[-1] == call.top(a=-1).bottom()
True
__class__

Обычно атрибут __class__ объекта возвращает его тип. Для объекта-макета с атрибутом spec вместо него __class__ возвращает класс. Это позволяет объектам-макетам проходить isinstance() тесты для объекта, который они заменяют/маскируют:

>>> mock = Mock(spec=3)
>>> isinstance(mock, int)
True

__class__ является назначаемым, это позволяет макету пройти проверку на isinstance(), не заставляя вас использовать спецификацию:

>>> mock = Mock()
>>> mock.__class__ = dict
>>> isinstance(mock, dict)
True
class unittest.mock.NonCallableMock(spec=None, wraps=None, name=None, spec_set=None, **kwargs)

Невызываемая версия Mock. Параметры конструктора имеют то же значение, что и в Mock, за исключением return_value и side_effect, которые не имеют значения в невызываемом mock.

Mock-объекты, использующие класс или экземпляр в качестве spec или spec_set, могут проходить isinstance() тесты:

>>> mock = Mock(spec=SomeClass)
>>> isinstance(mock, SomeClass)
True
>>> mock = Mock(spec_set=SomeClass())
>>> isinstance(mock, SomeClass)
True

В классах Mock есть поддержка подражания магическим методам. Подробную информацию см. в разделе magic methods.

Классы mock и декораторы patch() принимают произвольные ключевые аргументы для настройки. Для декораторов patch() ключевые слова передаются в конструктор создаваемого mock. Аргументы ключевых слов служат для настройки атрибутов имитатора:

>>> m = MagicMock(attribute=3, other='fish')
>>> m.attribute
3
>>> m.other
'fish'

Возвращаемое значение и побочный эффект дочерних моков можно задать таким же образом, используя точечную нотацию. Поскольку вы не можете использовать точечные имена непосредственно в вызове, вам придется создать словарь и распаковать его с помощью **:

>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock = Mock(some_attribute='eggs', **attrs)
>>> mock.some_attribute
'eggs'
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

Вызываемый имитатор, созданный с помощью spec (или spec_set), будет интроспектировать сигнатуру объекта спецификации при проверке вызовов имитатора. Таким образом, он может сопоставить аргументы фактического вызова независимо от того, были ли они переданы позиционно или по имени:

>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, c=3)
<Mock name='mock()' id='140161580456576'>
>>> mock.assert_called_with(1, 2, 3)
>>> mock.assert_called_with(a=1, b=2, c=3)

Это относится к assert_called_with(), assert_called_once_with(), assert_has_calls() и assert_any_call(). При Автоспецификация это также будет применяться к вызовам методов на объекте mock.

Изменено в версии 3.4: Добавлена интроспекция сигнатур для специфицированных и автоспецифицированных mock-объектов.

class unittest.mock.PropertyMock(*args, **kwargs)

Макет, предназначенный для использования в качестве property или другого descriptor класса. PropertyMock предоставляет методы __get__() и __set__(), чтобы вы могли указать возвращаемое значение, когда оно будет получено.

Получение экземпляра PropertyMock из объекта вызывает имитатор без каких-либо аргументов. Установка значения вызывает имитатор с установленным значением.

>>> class Foo:
...     @property
...     def foo(self):
...         return 'something'
...     @foo.setter
...     def foo(self, value):
...         pass
...
>>> with patch('__main__.Foo.foo', new_callable=PropertyMock) as mock_foo:
...     mock_foo.return_value = 'mockity-mock'
...     this_foo = Foo()
...     print(this_foo.foo)
...     this_foo.foo = 6
...
mockity-mock
>>> mock_foo.mock_calls
[call(), call(6)]

Из-за того, как хранятся атрибуты mock, вы не можете напрямую прикрепить PropertyMock к объекту mock. Вместо этого вы можете прикрепить его к объекту типа mock:

>>> m = MagicMock()
>>> p = PropertyMock(return_value=3)
>>> type(m).foo = p
>>> m.foo
3
>>> p.assert_called_once_with()
class unittest.mock.AsyncMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)

Асинхронная версия MagicMock. Объект AsyncMock будет вести себя так, чтобы объект распознавался как асинхронная функция, а результат вызова был ожидаемым.

>>> mock = AsyncMock()
>>> asyncio.iscoroutinefunction(mock)
True
>>> inspect.isawaitable(mock())  
True

Результат mock() - это асинхронная функция, которая после ожидания будет иметь результат side_effect или return_value:

  • Если side_effect - это функция, то async-функция вернет результат этой функции,

  • Если side_effect является исключением, функция async вызовет исключение,

  • Если side_effect является итерируемой переменной, то async-функция вернет следующее значение итерируемой переменной, однако если последовательность результатов исчерпана, то немедленно будет поднята StopAsyncIteration,

  • Если side_effect не определен, функция async вернет значение, определенное return_value, поэтому по умолчанию функция async возвращает новый объект AsyncMock.

Установка spec Mock или MagicMock в async-функцию приведет к тому, что после вызова будет возвращен объект coroutine.

>>> async def async_func(): pass
...
>>> mock = MagicMock(async_func)
>>> mock
<MagicMock spec='function' id='...'>
>>> mock()  
<coroutine object AsyncMockMixin._mock_call at ...>

Установка spec Mock, MagicMock или AsyncMock для класса с асинхронными и синхронными функциями автоматически определит синхронные функции и установит для них значение MagicMock (если родительский mock - AsyncMock или MagicMock) или Mock (если родительский mock - Mock). Все асинхронные функции будут иметь значение AsyncMock.

>>> class ExampleClass:
...     def sync_foo():
...         pass
...     async def async_foo():
...         pass
...
>>> a_mock = AsyncMock(ExampleClass)
>>> a_mock.sync_foo
<MagicMock name='mock.sync_foo' id='...'>
>>> a_mock.async_foo
<AsyncMock name='mock.async_foo' id='...'>
>>> mock = Mock(ExampleClass)
>>> mock.sync_foo
<Mock name='mock.sync_foo' id='...'>
>>> mock.async_foo
<AsyncMock name='mock.async_foo' id='...'>

Added in version 3.8.

assert_awaited()

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

>>> mock = AsyncMock()
>>> async def main(coroutine_mock):
...     await coroutine_mock
...
>>> coroutine_mock = mock()
>>> mock.called
True
>>> mock.assert_awaited()
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited.
>>> asyncio.run(main(coroutine_mock))
>>> mock.assert_awaited()
assert_awaited_once()

Утверждаем, что мейк ожидался ровно один раз.

>>> mock = AsyncMock()
>>> async def main():
...     await mock()
...
>>> asyncio.run(main())
>>> mock.assert_awaited_once()
>>> asyncio.run(main())
>>> mock.assert_awaited_once()
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited once. Awaited 2 times.
assert_awaited_with(*args, **kwargs)

Утверждает, что последний await был выполнен с указанными аргументами.

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_with('foo', bar='bar')
>>> mock.assert_awaited_with('other')
Traceback (most recent call last):
...
AssertionError: expected await not found.
Expected: mock('other')
Actual: mock('foo', bar='bar')
assert_awaited_once_with(*args, **kwargs)

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

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_once_with('foo', bar='bar')
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_once_with('foo', bar='bar')
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited once. Awaited 2 times.
assert_any_await(*args, **kwargs)

Утверждает, что этот мейк когда-либо ожидался с указанными аргументами.

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> asyncio.run(main('hello'))
>>> mock.assert_any_await('foo', bar='bar')
>>> mock.assert_any_await('other')
Traceback (most recent call last):
...
AssertionError: mock('other') await not found
assert_has_awaits(calls, any_order=False)

Утверждает, что мейк был ожидаем указанными вызовами. Список await_args_list проверяется на наличие ожиданий.

Если any_order равно false, то ожидания должны быть последовательными. Могут быть дополнительные вызовы до или после указанных ожиданий.

Если any_order равно true, то ожидания могут располагаться в любом порядке, но все они должны появляться в await_args_list.

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> calls = [call("foo"), call("bar")]
>>> mock.assert_has_awaits(calls)
Traceback (most recent call last):
...
AssertionError: Awaits not found.
Expected: [call('foo'), call('bar')]
Actual: []
>>> asyncio.run(main('foo'))
>>> asyncio.run(main('bar'))
>>> mock.assert_has_awaits(calls)
assert_not_awaited()

Утверждайте, что насмешек никто не ждал.

>>> mock = AsyncMock()
>>> mock.assert_not_awaited()
reset_mock(*args, **kwargs)

См. Mock.reset_mock(). Также устанавливает await_count в 0, await_args в None и очищает await_args_list.

await_count

Целое число, отслеживающее, сколько раз объект mock был ожидаем.

>>> mock = AsyncMock()
>>> async def main():
...     await mock()
...
>>> asyncio.run(main())
>>> mock.await_count
1
>>> asyncio.run(main())
>>> mock.await_count
2
await_args

Это либо None (если мейк не ожидался), либо аргументы, с которыми мейк ожидался в последний раз. Функционирует так же, как и Mock.call_args.

>>> mock = AsyncMock()
>>> async def main(*args):
...     await mock(*args)
...
>>> mock.await_args
>>> asyncio.run(main('foo'))
>>> mock.await_args
call('foo')
>>> asyncio.run(main('bar'))
>>> mock.await_args
call('bar')
await_args_list

Это список всех ожиданий, сделанных для объекта mock в последовательности (поэтому длина списка равна количеству ожиданий). Пока не было сделано ни одного ожидания, это пустой список.

>>> mock = AsyncMock()
>>> async def main(*args):
...     await mock(*args)
...
>>> mock.await_args_list
[]
>>> asyncio.run(main('foo'))
>>> mock.await_args_list
[call('foo')]
>>> asyncio.run(main('bar'))
>>> mock.await_args_list
[call('foo'), call('bar')]
class unittest.mock.ThreadingMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, *, timeout=UNSET, **kwargs)

Версия MagicMock для многопоточных тестов. Объект ThreadingMock предоставляет дополнительные методы для ожидания вызова, а не для немедленного утверждения о его выполнении.

Тайм-аут по умолчанию задается аргументом timeout или, если он не задан, атрибутом ThreadingMock.DEFAULT_TIMEOUT, который по умолчанию блокирует (None).

Вы можете настроить глобальный тайм-аут по умолчанию, установив значение ThreadingMock.DEFAULT_TIMEOUT.

wait_until_called(*, timeout=UNSET)

Ждет, пока не будет вызван имитатор.

Если таймаут был передан при создании mock или если этой функции передан аргумент таймаута, функция поднимает AssertionError, если вызов не был выполнен вовремя.

>>> mock = ThreadingMock()
>>> thread = threading.Thread(target=mock)
>>> thread.start()
>>> mock.wait_until_called(timeout=1)
>>> thread.join()
wait_until_any_call_with(*args, **kwargs)

Ждет, пока не будет вызван mock с указанными аргументами.

Если при создании макета был задан таймаут, функция выдает ошибку AssertionError, если вызов не был выполнен вовремя.

>>> mock = ThreadingMock()
>>> thread = threading.Thread(target=mock, args=("arg1", "arg2",), kwargs={"arg": "thing"})
>>> thread.start()
>>> mock.wait_until_any_call_with("arg1", "arg2", arg="thing")
>>> thread.join()
DEFAULT_TIMEOUT

Глобальный таймаут по умолчанию в секундах для создания экземпляров ThreadingMock.

Added in version 3.13.

Звонок на

Объекты Mock можно вызывать. Вызов возвращает значение, заданное в качестве атрибута return_value. Возвращаемое значение по умолчанию - это новый объект Mock; он создается при первом обращении к возвращаемому значению (явно или путем вызова Mock), но сохраняется и возвращается каждый раз одно и то же.

Обращения к объекту будут записываться в атрибуты типа call_args и call_args_list.

Если установлен параметр side_effect, то он будет вызван после того, как вызов будет записан, поэтому, если side_effect вызовет исключение, вызов все равно будет записан.

Самый простой способ заставить mock поднимать исключение при вызове - это сделать side_effect классом или экземпляром исключения:

>>> m = MagicMock(side_effect=IndexError)
>>> m(1, 2, 3)
Traceback (most recent call last):
  ...
IndexError
>>> m.mock_calls
[call(1, 2, 3)]
>>> m.side_effect = KeyError('Bang!')
>>> m('two', 'three', 'four')
Traceback (most recent call last):
  ...
KeyError: 'Bang!'
>>> m.mock_calls
[call(1, 2, 3), call('two', 'three', 'four')]

Если side_effect - это функция, то все, что возвращает эта функция, возвращает и вызов mock. Функция side_effect вызывается с теми же аргументами, что и имитатор. Это позволяет динамически изменять возвращаемое значение вызова в зависимости от входных данных:

>>> def side_effect(value):
...     return value + 1
...
>>> m = MagicMock(side_effect=side_effect)
>>> m(1)
2
>>> m(2)
3
>>> m.mock_calls
[call(1), call(2)]

Если вы хотите, чтобы имитатор по-прежнему возвращал значение по умолчанию (новый имитатор) или любое заданное значение, есть два способа сделать это. Либо вернуть mock.return_value изнутри side_effect, либо вернуть DEFAULT:

>>> m = MagicMock()
>>> def side_effect(*args, **kwargs):
...     return m.return_value
...
>>> m.side_effect = side_effect
>>> m.return_value = 3
>>> m()
3
>>> def side_effect(*args, **kwargs):
...     return DEFAULT
...
>>> m.side_effect = side_effect
>>> m()
3

Чтобы удалить side_effect и вернуться к поведению по умолчанию, установите side_effect на None:

>>> m = MagicMock(return_value=6)
>>> def side_effect(*args, **kwargs):
...     return 3
...
>>> m.side_effect = side_effect
>>> m()
3
>>> m.side_effect = None
>>> m()
6

В качестве side_effect также может выступать любой объект итерируемого множества. Повторные вызовы mock будут возвращать значения из итерируемого объекта (пока итерируемый объект не будет исчерпан и не будет вызван StopIteration):

>>> m = MagicMock(side_effect=[1, 2, 3])
>>> m()
1
>>> m()
2
>>> m()
3
>>> m()
Traceback (most recent call last):
  ...
StopIteration

Если какие-либо члены iterable являются исключениями, то они будут подняты, а не возвращены:

>>> iterable = (33, ValueError, 66)
>>> m = MagicMock(side_effect=iterable)
>>> m()
33
>>> m()
Traceback (most recent call last):
 ...
ValueError
>>> m()
66

Удаление атрибутов

Макетные объекты создают атрибуты по требованию. Это позволяет им притворяться объектами любого типа.

Вы можете захотеть, чтобы объект-макет возвращал False на вызов hasattr() или поднимал AttributeError при получении атрибута. Вы можете сделать это, предоставив объект в качестве spec для имитатора, но это не всегда удобно.

Вы «блокируете» атрибуты, удаляя их. После удаления доступ к атрибуту вызовет ошибку AttributeError.

>>> mock = MagicMock()
>>> hasattr(mock, 'm')
True
>>> del mock.m
>>> hasattr(mock, 'm')
False
>>> del mock.f
>>> mock.f
Traceback (most recent call last):
    ...
AttributeError: f

Подражательные имена и атрибут name

Поскольку «name» является аргументом конструктора Mock, если вы хотите, чтобы у вашего mock-объекта был атрибут «name», вы не можете просто передать его во время создания. Есть две альтернативы. Один вариант - использовать configure_mock():

>>> mock = MagicMock()
>>> mock.configure_mock(name='my_name')
>>> mock.name
'my_name'

Более простой вариант - просто установить атрибут «name» после создания mock:

>>> mock = MagicMock()
>>> mock.name = "foo"

Присоединение моков в качестве атрибутов

Когда вы прикрепляете имитатор как атрибут другого имитатора (или как возвращаемое значение), он становится «дочерним» по отношению к этому имитатору. Вызовы к дочернему мейку записываются в атрибуты method_calls и mock_calls родительского. Это полезно для настройки дочерних имитаторов и последующего прикрепления их к родительскому, или для прикрепления имитаторов к родительскому, который записывает все вызовы дочерних и позволяет делать утверждения о порядке вызовов между имитаторами:

>>> parent = MagicMock()
>>> child1 = MagicMock(return_value=None)
>>> child2 = MagicMock(return_value=None)
>>> parent.child1 = child1
>>> parent.child2 = child2
>>> child1(1)
>>> child2(2)
>>> parent.mock_calls
[call.child1(1), call.child2(2)]

Исключением является случай, когда у мейка есть имя. Это позволяет предотвратить «воспитание», если по какой-то причине вы не хотите, чтобы оно происходило.

>>> mock = MagicMock()
>>> not_a_child = MagicMock(name='not-a-child')
>>> mock.attribute = not_a_child
>>> mock.attribute()
<MagicMock name='not-a-child()' id='...'>
>>> mock.mock_calls
[]

Моки, созданные для вас с помощью patch(), автоматически получают имена. Чтобы прикрепить модели, у которых есть имена, к родителю, вы используете метод attach_mock():

>>> thing1 = object()
>>> thing2 = object()
>>> parent = MagicMock()
>>> with patch('__main__.thing1', return_value=None) as child1:
...     with patch('__main__.thing2', return_value=None) as child2:
...         parent.attach_mock(child1, 'child1')
...         parent.attach_mock(child2, 'child2')
...         child1('one')
...         child2('two')
...
>>> parent.mock_calls
[call.child1('one'), call.child2('two')]

Патчеры

Декораторы патчей используются для исправления объектов только в пределах области действия функции, которую они украшают. Они автоматически выполняют распаковку, даже если при этом возникают исключения. Все эти функции также можно использовать в операторах with или в качестве декораторов классов.

патч

Примечание

Главное - делать исправления в правильном пространстве имен. См. раздел where to patch.

unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

patch() действует как декоратор функций, декоратор классов или менеджер контекста. Внутри тела функции или оператора with, цель патчится новым объектом. При выходе из функции или оператора with патч отменяется.

Если значение new опущено, то target заменяется на AsyncMock, если исправляемый объект является async-функцией, или на MagicMock в противном случае. Если в качестве декоратора используется patch() и опущено значение new, то созданный mock передается в качестве дополнительного аргумента декорируемой функции. Если patch() используется в качестве менеджера контекста, созданный макет возвращается менеджером контекста.

target должна быть строкой в форме 'package.module.ClassName'. Импортируется цель, а указанный объект заменяется на новый объект, поэтому цель должна быть импортируема из среды, из которой вы вызываете patch(). Цель импортируется при выполнении декорированной функции, а не во время декорирования.

Аргументы spec и spec_set передаются в MagicMock, если патч создает его для вас.

Кроме того, вы можете передать spec=True или spec_set=True, что заставит патч передать объект, над которым издеваются, в качестве объекта spec/spec_set.

new_callable позволяет указать другой класс или вызываемый объект, который будет вызван для создания нового объекта. По умолчанию AsyncMock используется для async-функций, а MagicMock - для остальных.

Более мощной формой spec является autospec. Если вы установите значение autospec=True, то макет будет создан со спецификацией заменяемого объекта. Все атрибуты имитатора также будут иметь спецификацию соответствующего атрибута заменяемого объекта. У методов и функций, над которыми подражают, будут проверены аргументы, и если они будут вызваны с неправильной сигнатурой, то будет поднята ошибка TypeError. Для имитаторов, заменяющих класс, возвращаемое значение («экземпляр») будет иметь ту же спецификацию, что и класс. См. функции create_autospec() и Автоспецификация.

Вместо autospec=True вы можете передать autospec=some_object, чтобы использовать произвольный объект в качестве образца вместо заменяемого.

По умолчанию patch() не будет заменять несуществующие атрибуты. Если вы передадите create=True, а атрибут не существует, patch создаст атрибут при вызове исправленной функции и удалит его после выхода из исправленной функции. Это полезно для написания тестов против атрибутов, которые ваш производственный код создает во время выполнения. По умолчанию она отключена, поскольку может быть опасна. Включив его, вы можете писать проходные тесты против API, которых на самом деле не существует!

Примечание

Изменено в версии 3.5: Если вы исправляете встроенные модули в модуле, то вам не нужно передавать create=True, они будут добавлены по умолчанию.

Патч можно использовать в качестве декоратора классов TestCase. Он работает, украшая каждый тестовый метод в классе. Это позволяет сократить объем кода, когда ваши тестовые методы имеют общий набор патчей. patch() находит тесты, ища имена методов, которые начинаются с patch.TEST_PREFIX. По умолчанию это 'test', что совпадает с тем, как unittest находит тесты. Вы можете указать альтернативный префикс, задав patch.TEST_PREFIX.

Патчи можно использовать в качестве менеджера контекста вместе с оператором with. Здесь патч применяется к блоку с отступом после оператора with. Если вы используете «as», то патченный объект будет привязан к имени после «as»; очень полезно, если patch() создает для вас mock-объект.

patch() принимает произвольные аргументы в виде ключевых слов. Они будут переданы в AsyncMock, если исправляемый объект является асинхронным, в MagicMock в противном случае или в new_callable, если указано.

Для альтернативных вариантов использования доступны patch.dict(...), patch.multiple(...) и patch.object(...).

patch() в качестве декоратора функции, создавая для вас mock и передавая его в декорированную функцию:

>>> @patch('__main__.SomeClass')
... def function(normal_argument, mock_class):
...     print(mock_class is SomeClass)
...
>>> function(None)
True

Патчирование класса заменяет класс на MagicMock экземпляром. Если класс инстанцирован в тестируемом коде, то будет использован return_value mock.

Если класс инстанцируется несколько раз, вы можете использовать side_effect, чтобы каждый раз возвращать новый mock. В качестве альтернативы вы можете задать для return_value любое значение.

Чтобы настроить возвращаемые значения методов экземпляров в исправленном классе, вы должны сделать это на return_value. Например:

>>> class Class:
...     def method(self):
...         pass
...
>>> with patch('__main__.Class') as MockClass:
...     instance = MockClass.return_value
...     instance.method.return_value = 'foo'
...     assert Class() is instance
...     assert Class().method() == 'foo'
...

Если вы используете spec или spec_set и patch() заменяет класс, то возвращаемое значение созданного mock будет иметь тот же самый spec.

>>> Original = Class
>>> patcher = patch('__main__.Class', spec=True)
>>> MockClass = patcher.start()
>>> instance = MockClass()
>>> assert isinstance(instance, Original)
>>> patcher.stop()

Аргумент new_callable полезен в тех случаях, когда вы хотите использовать класс, альтернативный стандартному MagicMock для создаваемого mock. Например, если вы хотите, чтобы использовался NonCallableMock:

>>> thing = object()
>>> with patch('__main__.thing', new_callable=NonCallableMock) as mock_thing:
...     assert thing is mock_thing
...     thing()
...
Traceback (most recent call last):
  ...
TypeError: 'NonCallableMock' object is not callable

Другим вариантом использования может быть замена объекта на экземпляр io.StringIO:

>>> from io import StringIO
>>> def foo():
...     print('Something')
...
>>> @patch('sys.stdout', new_callable=StringIO)
... def test(mock_stdout):
...     foo()
...     assert mock_stdout.getvalue() == 'Something\n'
...
>>> test()

Когда patch() создает для вас имитатор, обычно первое, что вам нужно сделать, - это настроить его. Некоторые из этих настроек можно выполнить в вызове патча. Любые произвольные ключевые слова, которые вы передадите в вызов, будут использованы для установки атрибутов созданного макета:

>>> patcher = patch('__main__.thing', first='one', second='two')
>>> mock_thing = patcher.start()
>>> mock_thing.first
'one'
>>> mock_thing.second
'two'

Помимо атрибутов созданного мейка можно также настроить атрибуты дочерних мейков, например return_value и side_effect. Синтаксически их нельзя передавать напрямую в качестве аргументов ключевого слова, но словарь с такими ключами все же может быть расширен до вызова patch() с помощью **:

>>> config = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> patcher = patch('__main__.thing', **config)
>>> mock_thing = patcher.start()
>>> mock_thing.method()
3
>>> mock_thing.other()
Traceback (most recent call last):
  ...
KeyError

По умолчанию попытка исправить несуществующую функцию в модуле (или метод, или атрибут в классе) завершится неудачей с результатом AttributeError:

>>> @patch('sys.non_existing_attribute', 42)
... def test():
...     assert sys.non_existing_attribute == 42
...
>>> test()
Traceback (most recent call last):
  ...
AttributeError: <module 'sys' (built-in)> does not have the attribute 'non_existing_attribute'

но если добавить create=True в вызов patch(), то предыдущий пример будет работать, как и ожидалось:

>>> @patch('sys.non_existing_attribute', 42, create=True)
... def test(mock_stdout):
...     assert sys.non_existing_attribute == 42
...
>>> test()

Изменено в версии 3.8: patch() теперь возвращает AsyncMock, если цель является асинхронной функцией.

патч.объект

patch.object(target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

Сопоставить именованный член (атрибут) объекта (цель) с объектом-макетом.

patch.object() может использоваться как декоратор, декоратор класса или менеджер контекста. Аргументы new, spec, create, spec_set, autospec и new_callable имеют то же значение, что и для patch(). Как и patch(), patch.object() принимает произвольные аргументы ключевых слов для настройки создаваемого объекта mock.

При использовании в качестве декоратора класса patch.object() отдает честь patch.TEST_PREFIX в выборе методов для обертывания.

Вы можете вызвать patch.object() либо с тремя аргументами, либо с двумя. Форма с тремя аргументами принимает объект для исправления, имя атрибута и объект, на который нужно заменить атрибут.

При вызове в форме с двумя аргументами вы опускаете объект-заменитель, и для вас создается имитатор, который передается в качестве дополнительного аргумента декорированной функции:

>>> @patch.object(SomeClass, 'class_method')
... def test(mock_method):
...     SomeClass.class_method(3)
...     mock_method.assert_called_with(3)
...
>>> test()

spec, create и другие аргументы для patch.object() имеют то же значение, что и для patch().

patch.dict

patch.dict(in_dict, values=(), clear=False, **kwargs)

Исправьте словарь или объект, похожий на словарь, и верните словарь в исходное состояние после проверки.

in_dict может быть словарем или контейнером, подобным отображению. Если это отображение, то оно должно, по крайней мере, поддерживать получение, установку и удаление элементов, а также итерацию по ключам.

in_dict также может быть строкой, задающей имя словаря, который будет получен при импорте.

values может быть словарем значений, которые нужно установить в словарь. values также может быть итерируемым множеством пар (key, value).

Если значение clear равно true, то словарь будет очищен перед установкой новых значений.

patch.dict() также может быть вызван с произвольными аргументами в виде ключевых слов для установки значений в словаре.

Изменено в версии 3.8: patch.dict() теперь возвращает исправленный словарь при использовании в качестве менеджера контекста.

patch.dict() может использоваться как менеджер контекста, декоратор или декоратор класса:

>>> foo = {}
>>> @patch.dict(foo, {'newkey': 'newvalue'})
... def test():
...     assert foo == {'newkey': 'newvalue'}
...
>>> test()
>>> assert foo == {}

При использовании в качестве декоратора класса patch.dict() отдает предпочтение patch.TEST_PREFIX (по умолчанию 'test') при выборе методов для обертывания:

>>> import os
>>> import unittest
>>> from unittest.mock import patch
>>> @patch.dict('os.environ', {'newkey': 'newvalue'})
... class TestSample(unittest.TestCase):
...     def test_sample(self):
...         self.assertEqual(os.environ['newkey'], 'newvalue')

Если вы хотите использовать другой префикс для вашего теста, вы можете сообщить патчерам о другом префиксе, задав значение patch.TEST_PREFIX. Подробнее о том, как изменить значение TEST_PREFIX, смотрите в разделе TEST_PREFIX.

patch.dict() можно использовать для добавления членов в словарь или просто позволить тесту изменить словарь и убедиться, что словарь будет восстановлен после завершения теста.

>>> foo = {}
>>> with patch.dict(foo, {'newkey': 'newvalue'}) as patched_foo:
...     assert foo == {'newkey': 'newvalue'}
...     assert patched_foo == {'newkey': 'newvalue'}
...     # You can add, update or delete keys of foo (or patched_foo, it's the same dict)
...     patched_foo['spam'] = 'eggs'
...
>>> assert foo == {}
>>> assert patched_foo == {}
>>> import os
>>> with patch.dict('os.environ', {'newkey': 'newvalue'}):
...     print(os.environ['newkey'])
...
newvalue
>>> assert 'newkey' not in os.environ

Ключевые слова могут быть использованы в вызове patch.dict() для установки значений в словаре:

>>> mymodule = MagicMock()
>>> mymodule.function.return_value = 'fish'
>>> with patch.dict('sys.modules', mymodule=mymodule):
...     import mymodule
...     mymodule.function('some', 'args')
...
'fish'

patch.dict() можно использовать с объектами, похожими на словари, которые на самом деле не являются словарями. Как минимум, они должны поддерживать получение, установку, удаление элементов и итерацию или проверку принадлежности. Это соответствует магическим методам __getitem__(), __setitem__(), __delitem__() и либо __iter__(), либо __contains__().

>>> class Container:
...     def __init__(self):
...         self.values = {}
...     def __getitem__(self, name):
...         return self.values[name]
...     def __setitem__(self, name, value):
...         self.values[name] = value
...     def __delitem__(self, name):
...         del self.values[name]
...     def __iter__(self):
...         return iter(self.values)
...
>>> thing = Container()
>>> thing['one'] = 1
>>> with patch.dict(thing, one=2, two=3):
...     assert thing['one'] == 2
...     assert thing['two'] == 3
...
>>> assert thing['one'] == 1
>>> assert list(thing) == ['one']

patch.multiple

patch.multiple(target, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

Выполняет несколько исправлений за один вызов. Он принимает объект для исправления (либо как объект, либо как строку для получения объекта путем импорта) и аргументы ключевых слов для исправлений:

with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'):
    ...

Используйте DEFAULT в качестве значения, если вы хотите, чтобы patch.multiple() создавал мейки за вас. В этом случае созданные имитаторы передаются в декорированную функцию по ключевому слову, а словарь возвращается при использовании patch.multiple() в качестве менеджера контекста.

patch.multiple() может использоваться как декоратор, декоратор класса или менеджер контекста. Аргументы spec, spec_set, create, autospec и new_callable имеют то же значение, что и для patch(). Эти аргументы будут применяться ко всеми исправлениям, выполненным patch.multiple().

При использовании в качестве декоратора класса patch.multiple() отдает честь patch.TEST_PREFIX в выборе методов для обертывания.

Если вы хотите, чтобы patch.multiple() создавал за вас макеты, то в качестве значения можно использовать DEFAULT. Если вы используете patch.multiple() в качестве декоратора, то созданные имитаторы будут переданы в декорированную функцию по ключевому слову.

>>> thing = object()
>>> other = object()

>>> @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
... def test_function(thing, other):
...     assert isinstance(thing, MagicMock)
...     assert isinstance(other, MagicMock)
...
>>> test_function()

patch.multiple() может быть вложен в другие декораторы patch, но помещает аргументы, передаваемые по ключевому слову, после любого из стандартных аргументов, создаваемых patch():

>>> @patch('sys.exit')
... @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
... def test_function(mock_exit, other, thing):
...     assert 'other' in repr(other)
...     assert 'thing' in repr(thing)
...     assert 'exit' in repr(mock_exit)
...
>>> test_function()

Если в качестве менеджера контекста используется patch.multiple(), то значение, возвращаемое менеджером контекста, представляет собой словарь, в котором созданные имитаторы имеют ключи по имени:

>>> with patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) as values:
...     assert 'other' in repr(values['other'])
...     assert 'thing' in repr(values['thing'])
...     assert values['thing'] is thing
...     assert values['other'] is other
...

Методы исправления: запуск и остановка

У всех патчеров есть методы start() и stop(). С их помощью проще выполнять исправления в методах setUp или в тех случаях, когда требуется выполнить несколько исправлений без вложенных декораторов или с помощью операторов.

Чтобы использовать их, вызовите patch(), patch.object() или patch.dict(), как обычно, и сохраните ссылку на возвращенный объект patcher. Затем вы можете вызвать start(), чтобы установить патч на место, и stop(), чтобы отменить его.

Если вы используете patch() для создания имитатора, то он будет возвращен вызовом patcher.start.

>>> patcher = patch('package.module.ClassName')
>>> from package import module
>>> original = module.ClassName
>>> new_mock = patcher.start()
>>> assert module.ClassName is not original
>>> assert module.ClassName is new_mock
>>> patcher.stop()
>>> assert module.ClassName is original
>>> assert module.ClassName is not new_mock

Типичным примером может быть выполнение нескольких патчей в методе setUp метода TestCase:

>>> class MyTest(unittest.TestCase):
...     def setUp(self):
...         self.patcher1 = patch('package.module.Class1')
...         self.patcher2 = patch('package.module.Class2')
...         self.MockClass1 = self.patcher1.start()
...         self.MockClass2 = self.patcher2.start()
...
...     def tearDown(self):
...         self.patcher1.stop()
...         self.patcher2.stop()
...
...     def test_something(self):
...         assert package.module.Class1 is self.MockClass1
...         assert package.module.Class2 is self.MockClass2
...
>>> MyTest('test_something').run()

Осторожно

Если вы используете эту технику, вы должны убедиться, что исправление «отменено» вызовом stop. Это может оказаться сложнее, чем вы думаете, потому что если в setUp поднимается исключение, то tearDown не вызывается. unittest.TestCase.addCleanup() делает это проще:

>>> class MyTest(unittest.TestCase):
...     def setUp(self):
...         patcher = patch('package.module.Class')
...         self.MockClass = patcher.start()
...         self.addCleanup(patcher.stop)
...
...     def test_something(self):
...         assert package.module.Class is self.MockClass
...

В качестве дополнительного бонуса вам больше не нужно хранить ссылку на объект patcher.

Также можно остановить все запущенные патчи, используя patch.stopall().

patch.stopall()

Остановить все активные патчи. Останавливает только патчи, начатые с start.

встроенные модули патча

Вы можете патчить любые встроенные модули в модуле. Следующий пример исправляет встроенный модуль ord():

>>> @patch('__main__.ord')
... def test(mock_ord):
...     mock_ord.return_value = 101
...     print(ord('c'))
...
>>> test()
101

TEST_PREFIX

Все патчеры можно использовать как декораторы классов. При таком использовании они оборачивают каждый тестовый метод класса. Патчеры распознают методы, начинающиеся с 'test', как тестовые. Это тот же способ, которым unittest.TestLoader находит тестовые методы по умолчанию.

Возможно, вы захотите использовать другой префикс для своих тестов. Вы можете сообщить патчерам о другом префиксе, установив patch.TEST_PREFIX:

>>> patch.TEST_PREFIX = 'foo'
>>> value = 3
>>>
>>> @patch('__main__.value', 'not three')
... class Thing:
...     def foo_one(self):
...         print(value)
...     def foo_two(self):
...         print(value)
...
>>>
>>> Thing().foo_one()
not three
>>> Thing().foo_two()
not three
>>> value
3

Декораторы матрешки

Если вам нужно выполнить несколько патчей, вы можете просто сложить декораторы в стопку.

С помощью этого узора можно сложить несколько декоративных патчей:

>>> @patch.object(SomeClass, 'class_method')
... @patch.object(SomeClass, 'static_method')
... def test(mock1, mock2):
...     assert SomeClass.static_method is mock1
...     assert SomeClass.class_method is mock2
...     SomeClass.static_method('foo')
...     SomeClass.class_method('bar')
...     return mock1, mock2
...
>>> mock1, mock2 = test()
>>> mock1.assert_called_once_with('foo')
>>> mock2.assert_called_once_with('bar')

Обратите внимание, что декораторы применяются снизу вверх. Это стандартный способ применения декораторов в Python. Порядок созданных имитаторов, переданных в вашу тестовую функцию, соответствует этому порядку.

Где сделать заплатку

patch() работает путем (временной) замены объекта, на который указывает имя, на другой. На любой объект может указывать множество имен, поэтому для того, чтобы исправление работало, необходимо убедиться, что вы исправляете имя, используемое тестируемой системой.

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

Представьте, что у нас есть проект, который мы хотим протестировать, со следующей структурой:

a.py
    -> Defines SomeClass

b.py
    -> from a import SomeClass
    -> some_function instantiates SomeClass

Теперь мы хотим протестировать some_function, но для этого нужно подружить SomeClass с patch(). Проблема в том, что когда мы импортируем модуль b, что нам придется сделать, он импортирует SomeClass из модуля a. Если мы используем patch(), чтобы высмеять a.SomeClass, это никак не повлияет на наш тест; модуль b уже имеет ссылку на реальный SomeClass, и похоже, что наши исправления не повлияли.

Ключевым моментом является исправление SomeClass там, где он используется (или где его ищут). В данном случае some_function будет искать SomeClass в модуле b, куда мы его импортировали. Патчи должны выглядеть следующим образом:

@patch('b.SomeClass')

Однако рассмотрим альтернативный сценарий, когда вместо from a import SomeClass модуль b использует import a, а some_function - a.SomeClass. Обе эти формы импорта являются общими. В этом случае класс, который мы хотим исправить, ищется в модуле, поэтому вместо него мы должны исправить a.SomeClass:

@patch('a.SomeClass')

Дескрипторы исправлений и прокси-объекты

И patch, и patch.object корректно патчируют и восстанавливают дескрипторы: методы класса, статические методы и свойства. Их следует исправлять на классе, а не на экземпляре. Они также работают с некоторыми объектами, которые проксируют доступ к атрибутам, например django settings object.

MagicMock и поддержка магических методов

Издевательство над магическими методами

Mock поддерживает подражание методам протокола Python, также известным как «magic methods». Это позволяет подражательным объектам заменять контейнеры или другие объекты, реализующие протоколы Python.

Поскольку магические методы ищутся иначе, чем обычные методы [2], эта поддержка была реализована специально. Это означает, что поддерживаются только определенные магические методы. Список поддерживаемых методов включает почти все из них. Если вам не хватает каких-то методов, пожалуйста, сообщите нам об этом.

Вы подражаете магическим методам, устанавливая интересующий вас метод в качестве функции или подражаемого экземпляра. Если вы используете функцию, то она должна принимать self в качестве первого аргумента [3].

>>> def __str__(self):
...     return 'fooble'
...
>>> mock = Mock()
>>> mock.__str__ = __str__
>>> str(mock)
'fooble'
>>> mock = Mock()
>>> mock.__str__ = Mock()
>>> mock.__str__.return_value = 'fooble'
>>> str(mock)
'fooble'
>>> mock = Mock()
>>> mock.__iter__ = Mock(return_value=iter([]))
>>> list(mock)
[]

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

>>> mock = Mock()
>>> mock.__enter__ = Mock(return_value='foo')
>>> mock.__exit__ = Mock(return_value=False)
>>> with mock as m:
...     assert m == 'foo'
...
>>> mock.__enter__.assert_called_with()
>>> mock.__exit__.assert_called_with(None, None, None)

Вызовы магических методов не отображаются в method_calls, но записываются в mock_calls.

Примечание

Если вы используете ключевой аргумент spec для создания mock, то попытка установить магический метод, которого нет в спецификации, вызовет ошибку AttributeError.

Полный список поддерживаемых магических методов выглядит следующим образом:

  • __hash__, __sizeof__, __repr__ и __str__.

  • __dir__, __format__ и __subclasses__.

  • __round__, __floor__, __trunc__ и __ceil__.

  • Сравнения: __lt__, __gt__, __le__, __ge__, __eq__ и __ne__.

  • Методы контейнеров: __getitem__, __setitem__, __delitem__, __contains__, __len__, __iter__, __reversed__ и __missing__.

  • Контекстный менеджер: __enter__, __exit__, __aenter__ и __aexit__

  • Унарные числовые методы: __neg__, __pos__ и __invert__.

  • Числовые методы (включая варианты правой руки и in-place): __add__, __sub__, __mul__, __matmul__, __truediv__, __floordiv__, __mod__, __divmod__, __lshift__, __rshift__, __and__, __xor__, __or__ и __pow__.

  • Методы преобразования чисел: __complex__, __int__, __float__ и __index__.

  • Методы дескрипторов: __get__, __set__ и __delete__.

  • Маринование: __reduce__, __reduce_ex__, __getinitargs__, __getnewargs__, __getstate__ и __setstate__

  • Представление пути файловой системы: __fspath__

  • Методы асинхронной итерации: __aiter__ и __anext__.

Изменено в версии 3.8: Добавлена поддержка os.PathLike.__fspath__().

Изменено в версии 3.8: Добавлена поддержка __aenter__, __aexit__, __aiter__ и __anext__.

Следующие методы существуют, но не поддерживаются, поскольку они либо используются mock, либо не могут быть установлены динамически, либо могут вызвать проблемы:

  • __getattr__, __setattr__, __init__ и __new__.

  • __prepare__, __instancecheck__, __subclasscheck__, __del__

Волшебная насмешка

Существует два варианта MagicMock: MagicMock и NonCallableMagicMock.

class unittest.mock.MagicMock(*args, **kw)

MagicMock является подклассом Mock с реализацией по умолчанию большинства методов magic methods. Вы можете использовать MagicMock без необходимости самостоятельно настраивать магические методы.

Параметры конструктора имеют то же значение, что и для Mock.

Если вы используете аргументы spec или spec_set, то будут созданы только магические методы, которые существуют в спецификации.

class unittest.mock.NonCallableMagicMock(*args, **kw)

Не вызываемая версия MagicMock.

Параметры конструктора имеют то же значение, что и для MagicMock, за исключением return_value и side_effect, которые не имеют значения для невызываемого mock.

Магические методы настраиваются с помощью объектов MagicMock, поэтому вы можете настраивать их и использовать обычным способом:

>>> mock = MagicMock()
>>> mock[3] = 'fish'
>>> mock.__setitem__.assert_called_with(3, 'fish')
>>> mock.__getitem__.return_value = 'result'
>>> mock[2]
'result'

По умолчанию многие методы протокола должны возвращать объекты определенного типа. Эти методы предварительно сконфигурированы с возвращаемым значением по умолчанию, так что их можно использовать без необходимости делать что-либо, если возвращаемое значение вас не интересует. Вы все равно можете установить возвращаемое значение вручную, если хотите изменить значение по умолчанию.

Методы и их значения по умолчанию:

  • __lt__: NotImplemented

  • __gt__: NotImplemented

  • __le__: NotImplemented

  • __ge__: NotImplemented

  • __int__: 1

  • __contains__: False

  • __len__: 0

  • __iter__: iter([])

  • __exit__: False

  • __aexit__: False

  • __complex__: 1j

  • __float__: 1.0

  • __bool__: True

  • __index__: 1

  • __hash__: хэш по умолчанию для подражания

  • __str__: строка по умолчанию для подражания

  • __sizeof__: sizeof по умолчанию для mock

Например:

>>> mock = MagicMock()
>>> int(mock)
1
>>> len(mock)
0
>>> list(mock)
[]
>>> object() in mock
False

Два метода равенства, __eq__() и __ne__(), являются специальными. Они по умолчанию выполняют сравнение равенства по идентичности, используя атрибут side_effect, если вы не измените их возвращаемое значение на другое:

>>> MagicMock() == 3
False
>>> MagicMock() != 3
True
>>> mock = MagicMock()
>>> mock.__eq__.return_value = True
>>> mock == 3
True

Возвращаемое значение MagicMock.__iter__() может быть любым итерируемым объектом и не обязательно должно быть итератором:

>>> mock = MagicMock()
>>> mock.__iter__.return_value = ['a', 'b', 'c']
>>> list(mock)
['a', 'b', 'c']
>>> list(mock)
['a', 'b', 'c']

Если возвращаемое значение является итератором, то при однократной итерации он будет использован, а при последующих итерациях будет получен пустой список:

>>> mock.__iter__.return_value = iter(['a', 'b', 'c'])
>>> list(mock)
['a', 'b', 'c']
>>> list(mock)
[]

В MagicMock настроены все поддерживаемые методы магии, за исключением некоторых неясных и устаревших. Вы можете настроить их, если хотите.

Магические методы, которые поддерживаются, но не установлены по умолчанию в MagicMock, таковы:

  • __subclasses__

  • __dir__

  • __format__

  • __get__, __set__ и __delete__.

  • __reversed__ и __missing__

  • __reduce__, __reduce_ex__, __getinitargs__, __getnewargs__, __getstate__ и __setstate__.

  • __getformat__

Помощники

часовой

unittest.mock.sentinel

Объект sentinel обеспечивает удобный способ предоставления уникальных объектов для ваших тестов.

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

Изменено в версии 3.7: Атрибуты sentinel теперь сохраняют свою идентичность, когда они являются copied или pickled.

Иногда при тестировании необходимо проверить, что определенный объект передается в качестве аргумента в другой метод или возвращается. Для проверки этого обычно создаются именованные объекты-дозорные. sentinel предоставляет удобный способ создания и проверки идентичности таких объектов.

В этом примере обезьяний патч method возвращает sentinel.some_object:

>>> real = ProductionClass()
>>> real.method = Mock(name="method")
>>> real.method.return_value = sentinel.some_object
>>> result = real.method()
>>> assert result is sentinel.some_object
>>> result
sentinel.some_object

DEFAULT

unittest.mock.DEFAULT

Объект DEFAULT - это предварительно созданный дозорный (на самом деле sentinel.DEFAULT). Он может использоваться функциями side_effect для указания на то, что следует использовать обычное возвращаемое значение.

звоните

unittest.mock.call(*args, **kwargs)

call() - вспомогательный объект для создания более простых утверждений, для сравнения с call_args, call_args_list, mock_calls и method_calls. call() также может использоваться вместе с assert_has_calls().

>>> m = MagicMock(return_value=None)
>>> m(1, 2, a='foo', b='bar')
>>> m()
>>> m.call_args_list == [call(1, 2, a='foo', b='bar'), call()]
True
call.call_list()

Для объекта вызова, который представляет несколько вызовов, call_list() возвращает список всех промежуточных вызовов, а также конечный вызов.

call_list особенно полезен для утверждений о «цепочечных вызовах». Цепочечный вызов - это несколько вызовов в одной строке кода. Это приводит к появлению нескольких записей в mock_calls на mock. Ручное построение последовательности вызовов может быть утомительным.

call_list() может построить последовательность вызовов из одного и того же цепного вызова:

>>> m = MagicMock()
>>> m(1).method(arg='foo').other('bar')(2.0)
<MagicMock name='mock().method().other()()' id='...'>
>>> kall = call(1).method(arg='foo').other('bar')(2.0)
>>> kall.call_list()
[call(1),
 call().method(arg='foo'),
 call().method().other('bar'),
 call().method().other()(2.0)]
>>> m.mock_calls == kall.call_list()
True

Объект call - это либо кортеж из (позиционные аргументы, ключевые слова args), либо (имя, позиционные аргументы, ключевые слова args), в зависимости от того, как он был создан. Когда вы создаете их сами, это не особенно интересно, но объекты call, которые находятся в атрибутах Mock.call_args, Mock.call_args_list и Mock.mock_calls, можно интроспектировать, чтобы получить отдельные аргументы, которые они содержат.

Объекты call в Mock.call_args и Mock.call_args_list представляют собой два кортежа (positional args, keyword args), в то время как объекты call в Mock.mock_calls, а также те, которые вы создаете сами, представляют собой три кортежа (name, positional args, keyword args).

Вы можете использовать их «кортежность», чтобы извлечь отдельные аргументы для более сложной интроспекции и утверждений. Позиционные аргументы представляют собой кортеж (пустой кортеж, если позиционных аргументов нет), а аргументы ключевых слов - словарь:

>>> m = MagicMock(return_value=None)
>>> m(1, 2, 3, arg='one', arg2='two')
>>> kall = m.call_args
>>> kall.args
(1, 2, 3)
>>> kall.kwargs
{'arg': 'one', 'arg2': 'two'}
>>> kall.args is kall[0]
True
>>> kall.kwargs is kall[1]
True
>>> m = MagicMock()
>>> m.foo(4, 5, 6, arg='two', arg2='three')
<MagicMock name='mock.foo()' id='...'>
>>> kall = m.mock_calls[0]
>>> name, args, kwargs = kall
>>> name
'foo'
>>> args
(4, 5, 6)
>>> kwargs
{'arg': 'two', 'arg2': 'three'}
>>> name is m.mock_calls[0][0]
True

create_autospec

unittest.mock.create_autospec(spec, spec_set=False, instance=False, **kwargs)

Создайте объект-макет, используя другой объект в качестве образца. Атрибуты макета будут использовать соответствующий атрибут объекта spec в качестве образца.

Функции или методы, над которыми издеваются, будут проверять свои аргументы, чтобы убедиться, что они вызываются с правильной сигнатурой.

Если spec_set равен True, то попытка установить атрибуты, не существующие в объекте spec, приведет к ошибке AttributeError.

Если в качестве спецификации используется класс, то возвращаемое значение mock (экземпляр класса) будет иметь ту же спецификацию. Вы можете использовать класс в качестве спецификации для объекта экземпляра, передав instance=True. Возвращаемый имитатор будет доступен для вызова только в том случае, если экземпляры имитатора доступны для вызова.

create_autospec() также принимает произвольные аргументы в виде ключевых слов, которые передаются в конструктор созданного mock.

Примеры использования автоспецификации с помощью create_autospec() и аргумента autospec в patch() смотрите в Автоспецификация.

Изменено в версии 3.8: create_autospec() теперь возвращает AsyncMock, если цель является асинхронной функцией.

ЛЮБОЙ

unittest.mock.ANY

Иногда вам может понадобиться сделать утверждения о некоторых аргументах в вызове mock, но некоторые аргументы вас не интересуют или вы хотите вытащить их по отдельности из call_args и сделать на них более сложные утверждения.

Чтобы игнорировать определенные аргументы, можно передать объекты, которые сравниваются с всеми. Тогда вызовы assert_called_with() и assert_called_once_with() будут успешными независимо от того, что было передано.

>>> mock = Mock(return_value=None)
>>> mock('foo', bar=object())
>>> mock.assert_called_once_with('foo', bar=ANY)

ANY также можно использовать в сравнении со списками вызовов, например mock_calls:

>>> m = MagicMock(return_value=None)
>>> m(1)
>>> m(1, 2)
>>> m(object())
>>> m.mock_calls == [call(1), call(1, 2), ANY]
True

ANY не ограничивается сравнениями с объектами вызова и поэтому также может использоваться в тестовых утверждениях:

class TestStringMethods(unittest.TestCase):

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', ANY])

FILTER_DIR

unittest.mock.FILTER_DIR

FILTER_DIR - это переменная уровня модуля, которая управляет тем, как подражаемые объекты реагируют на dir(). По умолчанию используется True, который использует фильтрацию, описанную ниже, чтобы показывать только полезные члены. Если вам не нравится такая фильтрация или вам нужно отключить ее в диагностических целях, установите mock.FILTER_DIR = False.

При включенной фильтрации dir(some_mock) показывает только полезные атрибуты и включает любые динамически созданные атрибуты, которые обычно не показываются. Если имитатор был создан с помощью spec (или autospec, конечно), то будут показаны все атрибуты оригинала, даже если к ним еще не было доступа:

>>> dir(Mock())
['assert_any_call',
 'assert_called',
 'assert_called_once',
 'assert_called_once_with',
 'assert_called_with',
 'assert_has_calls',
 'assert_not_called',
 'attach_mock',
 ...
>>> from urllib import request
>>> dir(Mock(spec=request))
['AbstractBasicAuthHandler',
 'AbstractDigestAuthHandler',
 'AbstractHTTPHandler',
 'BaseHandler',
 ...

Многие не очень полезные (частные для Mock, а не для объекта имитации) атрибуты с префиксом подчеркивания и двойного подчеркивания были отфильтрованы из результата вызова dir() на Mock. Если вам не нравится такое поведение, вы можете отключить его, установив переключатель уровня модуля FILTER_DIR:

>>> from unittest import mock
>>> mock.FILTER_DIR = False
>>> dir(mock.Mock())
['_NonCallableMock__get_return_value',
 '_NonCallableMock__get_side_effect',
 '_NonCallableMock__return_value_doc',
 '_NonCallableMock__set_return_value',
 '_NonCallableMock__set_side_effect',
 '__call__',
 '__class__',
 ...

В качестве альтернативы вы можете просто использовать vars(my_mock) (члены экземпляра) и dir(type(my_mock)) (члены типа), чтобы обойти фильтрацию независимо от mock.FILTER_DIR.

mock_open

unittest.mock.mock_open(mock=None, read_data=None)

Вспомогательная функция для создания макета, заменяющего использование open(). Она работает для open(), вызываемого напрямую или используемого в качестве менеджера контекста.

Аргумент mock - это объект mock, который необходимо настроить. Если None (по умолчанию), то для вас будет создан объект MagicMock, API которого будет ограничен методами или атрибутами, доступными для стандартных файловых дескрипторов.

read_data - это строка для возврата методами read(), readline() и readlines() файлового хэндла. Вызовы этих методов будут брать данные из read_data до тех пор, пока они не иссякнут. Имитация этих методов довольно проста: каждый раз, когда вызывается mock, read_data перематывается на начало. Если вам нужно больше контроля над данными, которые вы передаете тестируемому коду, вам придется настроить этот имитатор под себя. Если этого недостаточно, один из пакетов in-memory filesystem на PyPI может предложить реалистичную файловую систему для тестирования.

Изменено в версии 3.4: Добавлена поддержка readline() и readlines(). Макет read() изменен, чтобы потреблять read_data, а не возвращать его при каждом вызове.

Изменено в версии 3.5: Теперь read_data сбрасывается при каждом обращении к mock.

Изменено в версии 3.8: В реализацию добавлен __iter__(), чтобы итерация (например, в циклах for) корректно потребляла read_data.

Использование open() в качестве менеджера контекста - отличный способ обеспечить правильное закрытие обработчиков файлов и становится все более распространенным:

with open('/some/path', 'w') as f:
    f.write('something')

Проблема в том, что даже если вы высмеиваете вызов open(), именно возвращаемый объект используется в качестве менеджера контекста (и у него вызываются __enter__() и __exit__()).

Подразнивание менеджеров контекста с помощью MagicMock встречается достаточно часто и достаточно хлопотно, чтобы вспомогательная функция была полезной.

>>> m = mock_open()
>>> with patch('__main__.open', m):
...     with open('foo', 'w') as h:
...         h.write('some stuff')
...
>>> m.mock_calls
[call('foo', 'w'),
 call().__enter__(),
 call().write('some stuff'),
 call().__exit__(None, None, None)]
>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')

И для чтения файлов:

>>> with patch('__main__.open', mock_open(read_data='bibble')) as m:
...     with open('foo') as h:
...         result = h.read()
...
>>> m.assert_called_once_with('foo')
>>> assert result == 'bibble'

Автоспецификация

Автоспецификация основана на существующей функции spec в mock. Она ограничивает api имитаторов api оригинального объекта (спецификации), но при этом является рекурсивной (реализована лениво), так что атрибуты имитаторов имеют только те же api, что и атрибуты спецификации. Кроме того, подражаемые функции/методы имеют ту же сигнатуру вызова, что и оригинальные, поэтому при неправильном вызове они вызывают ошибку TypeError.

Прежде чем я объясню, как работает автоспецификация, расскажу, зачем она нужна.

Mock - это очень мощный и гибкий объект, но он страдает от недостатка, который является общим для мокинга. Если вы рефакторите часть своего кода, переименуете члены и так далее, все тесты для кода, который все еще использует старый api, но использует mocks вместо реальных объектов, все равно пройдут. Это означает, что все ваши тесты пройдут, даже если ваш код сломан.

Изменено в версии 3.5: До версии 3.5 тесты с опечаткой в слове assert молча проходили, когда должны были вызывать ошибку. Вы все еще можете добиться такого поведения, передав в Mock значение unsafe=True.

Обратите внимание, что это еще одна причина, по которой вам нужны интеграционные тесты, а также тесты модулей. Тестирование всего по отдельности - это хорошо и замечательно, но если вы не проверяете, как ваши модули «соединены», то остается много места для ошибок, которые могли бы быть обнаружены тестами.

В mock уже предусмотрена функция, помогающая в этом, которая называется speccing. Если вы используете класс или экземпляр в качестве spec для имитатора, вы можете получить доступ к атрибутам имитатора только в том случае, если они существуют в реальном классе:

>>> from urllib import request
>>> mock = Mock(spec=request.Request)
>>> mock.assret_called_with  # Intentional typo!
Traceback (most recent call last):
 ...
AttributeError: Mock object has no attribute 'assret_called_with'

Спецификация относится только к самому макету, поэтому мы по-прежнему имеем ту же проблему с любыми методами на макете:

>>> mock.has_data()
<mock.Mock object at 0x...>
>>> mock.has_data.assret_called_with()  # Intentional typo!

Автоспецификация решает эту проблему. Вы можете либо передать autospec=True в patch() / patch.object(), либо использовать функцию create_autospec() для создания макета со спецификацией. Если вы используете аргумент autospec=True для patch(), то объект, который заменяется, будет использоваться в качестве объекта спецификации. Поскольку спецификация выполняется «лениво» (спецификация создается по мере обращения к атрибутам макета), вы можете использовать ее с очень сложными или глубоко вложенными объектами (например, модулями, которые импортируют модули, которые импортируют модули) без большого ущерба для производительности.

Вот пример его использования:

>>> from urllib import request
>>> patcher = patch('__main__.request', autospec=True)
>>> mock_request = patcher.start()
>>> request is mock_request
True
>>> mock_request.Request
<MagicMock name='request.Request' spec='Request' id='...'>

Видно, что у request.Request есть спецификация. request.Request принимает два аргумента в конструкторе (один из которых - self). Вот что произойдет, если мы попытаемся вызвать его неправильно:

>>> req = request.Request()
Traceback (most recent call last):
 ...
TypeError: <lambda>() takes at least 2 arguments (1 given)

Спецификация также применяется к инстантным классам (т.е. к возвращаемым значениям специальных имитаторов):

>>> req = request.Request('foo')
>>> req
<NonCallableMagicMock name='request.Request()' spec='Request' id='...'>

Объекты Request не являются вызываемыми, поэтому возвращаемое значение при инстанцировании нашего насмешника request.Request - это не вызываемый насмешник. Благодаря этой спецификации любые опечатки в наших утверждениях будут вызывать правильную ошибку:

>>> req.add_header('spam', 'eggs')
<MagicMock name='request.Request().add_header()' id='...'>
>>> req.add_header.assret_called_with  # Intentional typo!
Traceback (most recent call last):
 ...
AttributeError: Mock object has no attribute 'assret_called_with'
>>> req.add_header.assert_called_with('spam', 'eggs')

Во многих случаях вы просто сможете добавить autospec=True к существующим вызовам patch(), и тогда вы будете защищены от ошибок, связанных с опечатками и изменениями api.

Помимо использования autospec через patch(), существует create_autospec() для создания автоспецифицированных mocks напрямую:

>>> from urllib import request
>>> mock_request = create_autospec(request)
>>> mock_request.Request('foo', 'bar')
<NonCallableMagicMock name='mock.Request()' spec='Request' id='...'>

Однако это не без оговорок и ограничений, поэтому такое поведение не является поведением по умолчанию. Чтобы узнать, какие атрибуты доступны для объекта-образца, autospec должен проинтроспектировать (получить доступ к атрибутам) образец. По мере того как вы обходите атрибуты на макете, под капотом происходит соответствующий обход исходного объекта. Если какой-либо из ваших специфицированных объектов имеет свойства или дескрипторы, которые могут вызвать выполнение кода, то вы не сможете использовать autospec. С другой стороны, гораздо лучше проектировать свои объекты так, чтобы интроспекция была безопасной [4].

Более серьезной проблемой является то, что часто атрибуты экземпляра создаются в методе __init__() и не существуют в классе вообще. autospec не может знать о динамически созданных атрибутах и ограничивает api видимыми атрибутами.

>>> class Something:
...   def __init__(self):
...     self.a = 33
...
>>> with patch('__main__.Something', autospec=True):
...   thing = Something()
...   thing.a
...
Traceback (most recent call last):
  ...
AttributeError: Mock object has no attribute 'a'

Существует несколько различных способов решения этой проблемы. Самый простой, но не всегда наименее раздражающий способ - просто установить необходимые атрибуты в mock после создания. То, что autospec не позволяет вам получать атрибуты, не существующие в спецификации, не мешает вам установить их:

>>> with patch('__main__.Something', autospec=True):
...   thing = Something()
...   thing.a = 33
...

Существует более агрессивная версия spec и autospec, которая препятствует установке несуществующих атрибутов. Это полезно, если вы хотите убедиться, что ваш код устанавливает только действительные атрибуты, но, очевидно, это предотвращает данный конкретный сценарий:

>>> with patch('__main__.Something', autospec=True, spec_set=True):
...   thing = Something()
...   thing.a = 33
...
Traceback (most recent call last):
 ...
AttributeError: Mock object has no attribute 'a'

Вероятно, лучший способ решения проблемы - добавить атрибуты класса в качестве значений по умолчанию для членов экземпляра, инициализированных в __init__(). Обратите внимание, что если вы задаете атрибуты по умолчанию только в __init__(), то предоставление их через атрибуты класса (разумеется, общие для экземпляров) тоже будет быстрее. Например.

class Something:
    a = 33

Это поднимает еще одну проблему. Относительно часто для членов, которые впоследствии станут объектами другого типа, по умолчанию задается значение None. Значение None было бы бесполезным в качестве спецификации, поскольку оно не позволяло бы вам получить доступ к любым атрибутам или методам этого объекта. Поскольку None никогда не будет полезен как спецификация и, вероятно, указывает на член, который обычно будет иметь другой тип, autospec не использует спецификацию для членов, установленных в None. Это будут обычные моки (хорошо - MagicMocks):

>>> class Something:
...     member = None
...
>>> mock = create_autospec(Something)
>>> mock.member.foo.bar.baz()
<MagicMock name='mock.member.foo.bar.baz()' id='...'>

Если модификация производственных классов для добавления значений по умолчанию вам не по душе, то есть и другие варианты. Один из них - просто использовать экземпляр в качестве спецификации, а не класс. Другой вариант - создать подкласс производственного класса и добавить параметры по умолчанию в подкласс, не затрагивая производственный класс. Оба варианта требуют использования альтернативного объекта в качестве спецификации. К счастью, patch() поддерживает это - вы можете просто передать альтернативный объект в качестве аргумента autospec:

>>> class Something:
...   def __init__(self):
...     self.a = 33
...
>>> class SomethingForTest(Something):
...   a = 33
...
>>> p = patch('__main__.Something', autospec=SomethingForTest)
>>> mock = p.start()
>>> mock.a
<NonCallableMagicMock name='Something.a' spec='int' id='...'>

Опечатывание имитаторов

unittest.mock.seal(mock)

Seal отключит автоматическое создание имитаторов при рекурсивном обращении к атрибутам запечатываемого имитатора или к любым его атрибутам, которые уже являются имитаторами.

Если экземпляр макета с именем или спецификацией назначен на атрибут, он не будет учитываться в цепочке запечатывания. Это позволяет предотвратить фиксацию части объекта-макета с помощью печати.

>>> mock = Mock()
>>> mock.submock.attribute1 = 2
>>> mock.not_submock = mock.Mock(name="sample_name")
>>> seal(mock)
>>> mock.new_attribute  # This will raise AttributeError.
>>> mock.submock.attribute2  # This will raise AttributeError.
>>> mock.not_submock.attribute2  # This won't raise.

Added in version 3.7.

Порядок старшинства side_effect, return_value и обертки

Порядок их старшинства таков:

  1. side_effect

  2. return_value

  3. обертывания

Если заданы все три значения, mock вернет значение из side_effect, игнорируя return_value и обернутый объект в целом. Если заданы любые два, то значение вернет тот из них, который имеет больший приоритет. Независимо от того, кто из них был задан первым, порядок старшинства остается неизменным.

>>> from unittest.mock import Mock
>>> class Order:
...     @staticmethod
...     def get_value():
...         return "third"
...
>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.get_value.side_effect = ["first"]
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'first'

Поскольку None является значением по умолчанию для side_effect, если вы переназначите его значение обратно на None, порядок старшинства будет проверяться между return_value и обернутым объектом, игнорируя side_effect.

>>> order_mock.get_value.side_effect = None
>>> order_mock.get_value()
'second'

Если значение, возвращаемое side_effect, равно DEFAULT, оно игнорируется, и порядок старшинства переходит к преемнику для получения возвращаемого значения.

>>> from unittest.mock import DEFAULT
>>> order_mock.get_value.side_effect = [DEFAULT]
>>> order_mock.get_value()
'second'

Если Mock обертывает объект, то по умолчанию будет использоваться значение return_value.

>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.return_value
sentinel.DEFAULT
>>> order_mock.get_value.return_value
sentinel.DEFAULT

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

Поскольку реальный вызов осуществляется к обернутому объекту, создание экземпляра этого имитатора вернет реальный экземпляр класса. Необходимо передать позиционные аргументы, если таковые требуются обернутому объекту.

>>> order_mock_instance = order_mock()
>>> isinstance(order_mock_instance, Order)
True
>>> order_mock_instance.get_value()
'third'
>>> order_mock.get_value.return_value = DEFAULT
>>> order_mock.get_value()
'third'
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'second'

Но если вы присвоите ему None, это не будет проигнорировано, так как это явное присвоение. Таким образом, порядок старшинства не перейдет к обернутому объекту.

>>> order_mock.get_value.return_value = None
>>> order_mock.get_value() is None
True

Даже если вы зададите все три параметра сразу при инициализации макета, порядок приоритета останется прежним:

>>> order_mock = Mock(spec=Order, wraps=Order,
...                   **{"get_value.side_effect": ["first"],
...                      "get_value.return_value": "second"}
...                   )
...
>>> order_mock.get_value()
'first'
>>> order_mock.get_value.side_effect = None
>>> order_mock.get_value()
'second'
>>> order_mock.get_value.return_value = DEFAULT
>>> order_mock.get_value()
'third'

Если side_effect исчерпан, то в соответствии с порядком старшинства значение не будет получено от преемников. Вместо этого будет вызвано исключение StopIteration.

>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.get_value.side_effect = ["first side effect value",
...                                     "another side effect value"]
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'first side effect value'
>>> order_mock.get_value()
'another side effect value'
>>> order_mock.get_value()
Traceback (most recent call last):
 ...
StopIteration