Assert, funkcja / metoda nie została wywołana przy użyciu Mocka

136

Używam biblioteki Mock do testowania mojej aplikacji, ale chcę zapewnić, że jakaś funkcja nie została wywołana. Mock doktorzy mówią o metodach takich jak mock.assert_called_withi mock.assert_called_once_with, ale nie znalazłem niczego podobnego mock.assert_not_calledlub związanego z weryfikacją, że mock NIE został wywołany .

Mógłbym użyć czegoś takiego jak poniżej, chociaż nie wydaje się to fajne ani pytoniczne:

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

Jakieś pomysły, jak to osiągnąć?

Gerard
źródło
Jak @Ahmet wskazuje w swojej odpowiedzi, funkcja assert_not_called jest teraz obsługiwana, również w backport ( docs.python.org/3/library/ ... ).
Martin

Odpowiedzi:

149

To powinno działać w Twoim przypadku;

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

Próba;

>>> 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
Joachim Isaksson
źródło
Czy ta odpowiedź wymaga Django? Pojawia się błąd:AttributeError: MockCallable instance has no attribute 'called'
Nathan Arthur
@NathanArthur Hm, nie sądzę, po sudo easy_install -U mocki from mock import Mockna MacOS, powyższe działa bez problemu. Nigdy nie instalowałem Django :)
Joachim Isaksson
Hmm. To dziwne. Używam Pythona 2.7.1 i używam unittest oraz from mock import MockPython Mock 0.1.0 do moich testów. Czy coś z tego brzmi problematycznie?
Nathan Arthur
Kpię z wywoływanej klasy z innego modułu, więc wygląda na to module_to_test.another_module.class = mock.Mock(), czy możesz potwierdzić, że to nie zapamiętuje wywołań w różnych przypadkach testowych (instancje unittest.TestCase)? Myślę, że licznik połączeń nie resetuje się w tym przypadku
0xc0de
71

Chociaż stare pytanie, chciałbym dodać, że obecnie mockbiblioteka (backport unittest.mock) obsługuje assert_not_calledmetodę.

Po prostu zaktualizuj swój;

pip install mock --upgrade

Ahmet
źródło
29

Możesz sprawdzić calledatrybut, ale jeśli Twoja asercja się nie powiedzie, następną rzeczą, którą będziesz chciał wiedzieć, jest coś o nieoczekiwanym wywołaniu, więc równie dobrze możesz zorganizować wyświetlanie tych informacji od samego początku. Używając unittest, możesz sprawdzić zawartość call_args_listzamiast:

self.assertItemsEqual(my_var.call_args_list, [])

Gdy to się nie powiedzie, wyświetla następujący komunikat:

AssertionError: Liczba elementów nie była równa:
Pierwsza ma 0, druga ma 1: call ('pierwszy argument', 4)
Rob Kennedy
źródło
14

Podczas testowania przy użyciu klasy dziedziczy unittest.TestCase możesz po prostu użyć metod takich jak:

  • assertTrue
  • assertFalse
  • assertEqual

i podobne (w dokumentacji Pythona znajdziesz resztę).

W naszym przykładzie możemy po prostu stwierdzić, czy właściwość mock_method.called ma wartość False , co oznacza, że ​​ta metoda nie została wywołana.

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
źródło
12

Ze python >= 3.5można użyć mock_object.assert_not_called().

valex
źródło
1

Sądząc po innych odpowiedziach, nikt oprócz @ rob-kennedy nie mówił o call_args_list.

To potężne narzędzie, dzięki któremu możesz wdrożyć dokładnie odwrotnie niż MagicMock.assert_called_with()

call_args_listto lista callobiektów. Każdy callobiekt reprezentuje wywołanie wykonane na fałszywym wywołaniu.

>>> 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)]

Korzystanie z callobiektu jest łatwe, ponieważ można go porównać z krotką o długości 2, w której pierwszy składnik jest krotką zawierającą wszystkie argumenty pozycyjne powiązanego wywołania, podczas gdy drugi składnik jest słownikiem słów kluczowych arguments.

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

Tak więc sposobem rozwiązania konkretnego problemu PO jest

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

Zauważ, że w ten sposób, zamiast po prostu sprawdzać, czy wywołano fałszywe wywołanie, MagicMock.calledmożesz teraz sprawdzić, czy zostało wywołane z określonym zestawem argumentów.

To przydatne. Powiedzmy, że chcesz przetestować funkcję, która pobiera listę i wywołuje inną funkcję compute(), dla każdej wartości listy tylko wtedy, gdy spełniają określony warunek.

Możesz teraz kpić computei sprawdzić, czy została wywołana na jakiejś wartości, ale nie na innych.

Giuseppe Crinò
źródło