Утверждение, что функция / метод не был вызван с использованием Mock

132

Я использую библиотеку Mock для тестирования своего приложения, но хочу заявить, что какая-то функция не была вызвана. В макетных документах говорится о таких методах, как mock.assert_called_withи mock.assert_called_once_with, но я не нашел ничего похожего mock.assert_not_calledили чего-то связанного с проверкой, что макет НЕ был вызван .

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

def test_something:
    # some actions
    with patch('something') as my_var:
        try:
            # args are not important. func should never be called in this test
            my_var.assert_called_with(some, args)
        except AssertionError:
            pass  # this error being raised means it's ok
    # other stuff

Есть идеи, как это сделать?

Gerard
источник
Как отмечает @Ahmet в своем ответе, assert_not_called теперь поддерживается, также в backport ( docs.python.org/3/library/… ).
Мартин

Ответы:

145

Это должно работать в вашем случае;

assert not my_var.called, 'method should not have been called'

Образец;

>>> mock=Mock()
>>> mock.a()
<Mock name='mock.a()' id='4349129872'>
>>> assert not mock.b.called, 'b was called and should not have been'
>>> assert not mock.a.called, 'a was called and should not have been'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: a was called and should not have been
Иоахим Исакссон
источник
Требуется ли для этого ответа Django? Я получаю сообщение об ошибке:AttributeError: MockCallable instance has no attribute 'called'
Натан Артур,
@NathanArthur Хм, я так не думаю, после MacOS sudo easy_install -U mockи from mock import Mockна MacOS все вышеперечисленное работает без сбоев. Никогда не устанавливал Django :)
Йоахим Исакссон
Хм. Странно. Я запускаю Python 2.7.1, использую unittest и from mock import MockPython Mock 0.1.0 для своих тестов. Что-нибудь из этого звучит проблематично?
Натан Артур
Я издеваюсь над вызываемым классом из другого модуля, поэтому, похоже module_to_test.another_module.class = mock.Mock(), вы можете подтвердить, что он не запоминает вызовы в разных тестовых случаях (экземпляры unittest.TestCase)? Я думаю, что в этом случае счетчик звонков не сбрасывается
0xc0de
69

Хотя это старый вопрос, я хотел бы добавить, что в настоящее время mockбиблиотека (backport of unittest.mock) поддерживает assert_not_calledметод.

Просто обновите свой;

pip install mock --upgrade

Ахмет
источник
29

Вы можете проверить calledатрибут, но если ваше утверждение терпит неудачу, следующее, что вам нужно знать, это что-то о неожиданном вызове, поэтому вы также можете организовать отображение этой информации с самого начала. Используя unittest, вы можете call_args_listвместо этого проверить содержимое :

self.assertItemsEqual(my_var.call_args_list, [])

В случае сбоя выдает следующее сообщение:

AssertionError: количество элементов не было равным:
Первый имеет 0, второй - 1: call ('первый аргумент', 4)
Роб Кеннеди
источник
14

Когда вы тестируете, используя класс, наследующий unittest.TestCase, вы можете просто использовать такие методы, как:

  • assertTrue
  • assertFalse
  • assertEqual

и тому подобное ( остальное вы найдете в документации Python ).

В вашем примере мы можем просто утверждать, имеет ли свойство mock_method.called значение False , что означает, что метод не был вызван.

import unittest
from unittest import mock

import my_module

class A(unittest.TestCase):
    def setUp(self):
        self.message = "Method should not be called. Called {times} times!"

    @mock.patch("my_module.method_to_mock")
    def test(self, mock_method):
        my_module.method_to_mock()

        self.assertFalse(mock_method.called,
                         self.message.format(times=mock_method.call_count))
Hunter_71
источник
11

С помощью python >= 3.5можно использовать mock_object.assert_not_called().

Валлекс
источник
1

Судя по другим ответам, никто, кроме @ rob-kennedy, не говорил о call_args_list.

Это мощный инструмент, позволяющий реализовать прямо противоположное MagicMock.assert_called_with()

call_args_listэто список callобъектов. Каждый callобъект представляет собой вызов фиктивного вызываемого объекта.

>>> from unittest.mock import MagicMock
>>> m = MagicMock()
>>> m.call_args_list
[]
>>> m(42)
<MagicMock name='mock()' id='139675158423872'>
>>> m.call_args_list
[call(42)]
>>> m(42, 30)
<MagicMock name='mock()' id='139675158423872'>
>>> m.call_args_list
[call(42), call(42, 30)]

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

>>> ((42,),) in m.call_args_list
True
>>> m(42, foo='bar')
<MagicMock name='mock()' id='139675158423872'>
>>> ((42,), {'foo': 'bar'}) in m.call_args_list
True
>>> m(foo='bar')
<MagicMock name='mock()' id='139675158423872'>
>>> ((), {'foo': 'bar'}) in m.call_args_list
True

Итак, способ решения конкретной проблемы OP -

def test_something():
    with patch('something') as my_var:
        assert ((some, args),) not in my_var.call_args_list

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

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

Теперь вы можете поиздеваться computeи проверить, было ли вызвано какое-то значение, а не другое.

Джузеппе Крино
источник