Ich verspotte einen Aufruf zur requests.post
Nutzung der Mock
Bibliothek:
requests.post = Mock()
Der Aufruf umfasst mehrere Argumente: die URL, eine Nutzlast, einige Authentifizierungsdaten usw. Ich möchte behaupten, dass diese requests.post
mit einer bestimmten URL aufgerufen wird, aber die anderen Argumente interessieren mich nicht. Wenn ich das versuche:
requests.post.assert_called_with(requests_arguments)
Der Test schlägt fehl, da erwartet wird, dass er nur mit diesem Argument aufgerufen wird.
Gibt es eine Möglichkeit zu überprüfen, ob irgendwo im Funktionsaufruf ein einzelnes Argument verwendet wird, ohne die anderen Argumente übergeben zu müssen?
Oder, noch besser, gibt es eine Möglichkeit, eine bestimmte URL zu bestätigen und dann Datentypen für die anderen Argumente zu abstrahieren (dh Daten sollten ein Wörterbuch sein, auth sollte eine Instanz von HTTPBasicAuth sein usw.)?
quelle
requests-mock
könnte das Modul auch interessant sein.Antworten:
Soweit ich weiß,
Mock
gibt es keine Möglichkeit, über das zu erreichen, was Sie wollenassert_called_with
. Sie könnten die zugreifencall_args
undcall_args_list
Mitglieder und die Behauptungen manuell durchführen.Dies ist jedoch eine einfache (und schmutzige) Methode, um fast das zu erreichen, was Sie wollen. Sie müssen eine Klasse implementieren, deren
__eq__
Methode immer Folgendes zurückgibtTrue
:def Any(cls): class Any(cls): def __eq__(self, other): return True return Any()
Verwenden Sie es als:
In [14]: caller = mock.Mock(return_value=None) In [15]: caller(1,2,3, arg=True) In [16]: caller.assert_called_with(Any(int), Any(int), Any(int), arg=True) In [17]: caller.assert_called_with(Any(int), Any(int), Any(int), arg=False) --------------------------------------------------------------------------- AssertionError Traceback (most recent call last) <ipython-input-17-c604faa06bd0> in <module>() ----> 1 caller.assert_called_with(Any(int), Any(int), Any(int), arg=False) /usr/lib/python3.3/unittest/mock.py in assert_called_with(_mock_self, *args, **kwargs) 724 if self.call_args != (args, kwargs): 725 msg = self._format_mock_failure_message(args, kwargs) --> 726 raise AssertionError(msg) 727 728 AssertionError: Expected call: mock(0, 0, 0, arg=False) Actual call: mock(1, 2, 3, arg=True)
Wie Sie sehen können, wird nur nach dem gesucht
arg
. Sie müssen Unterklassen von erstellenint
, sonst funktionieren die Vergleiche nicht 1 . Sie müssen jedoch noch alle Argumente angeben. Wenn Sie viele Argumente haben, können Sie Ihren Code mithilfe des Tupel-Entpackens verkürzen:In [18]: caller(1,2,3, arg=True) In [19]: caller.assert_called_with(*[Any(int)]*3, arg=True)
Abgesehen davon kann ich mir keine Möglichkeit vorstellen, zu vermeiden, dass alle Parameter an Sie übergeben
assert_called_with
und so bearbeitet werden, wie Sie es beabsichtigen.Die obige Lösung kann erweitert werden, um nach anderen Argumenttypen zu suchen. Zum Beispiel:
In [21]: def Any(cls): ...: class Any(cls): ...: def __eq__(self, other): ...: return isinstance(other, cls) ...: return Any() In [22]: caller(1, 2.0, "string", {1:1}, [1,2,3]) In [23]: caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(list)) In [24]: caller(1, 2.0, "string", {1:1}, [1,2,3]) In [25]: caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(tuple)) --------------------------------------------------------------------------- AssertionError Traceback (most recent call last) <ipython-input-25-f607a20dd665> in <module>() ----> 1 caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(tuple)) /usr/lib/python3.3/unittest/mock.py in assert_called_with(_mock_self, *args, **kwargs) 724 if self.call_args != (args, kwargs): 725 msg = self._format_mock_failure_message(args, kwargs) --> 726 raise AssertionError(msg) 727 728 AssertionError: Expected call: mock(0, 0.0, '', {}, ()) Actual call: mock(1, 2.0, 'string', {1: 1}, [1, 2, 3])
Dies erlaubt jedoch keine Argumente, die beispielsweise sowohl ein
int
als auch ein sein könnenstr
. Das Zulassen mehrerer Argumente fürAny
und die Verwendung von Mehrfachvererbung hilft nicht. Wir können dies mit lösenabc.ABCMeta
def Any(*cls): class Any(metaclass=abc.ABCMeta): def __eq__(self, other): return isinstance(other, cls) for c in cls: Any.register(c) return Any()
Beispiel:
In [41]: caller(1, "ciao") In [42]: caller.assert_called_with(Any(int, str), Any(int, str)) In [43]: caller("Hello, World!", 2) In [44]: caller.assert_called_with(Any(int, str), Any(int, str))
1 Ich habe den Namen
Any
für die Funktion verwendet, da sie im Code "als Klasse verwendet" wird. Auchany
ist ein eingebauter ...quelle
def __neq__(self, other): return False
.Sie können den
ANY
Helfer auch verwenden, um immer Argumente abzugleichen, die Sie nicht kennen oder nach denen Sie nicht suchen.Mehr zum ANY-Helfer: https://docs.python.org/3/library/unittest.mock.html#any
So könnten Sie beispielsweise das Argument 'Sitzung' wie folgt zuordnen:
from unittest.mock import ANY requests_arguments = {'slug': 'foo', 'session': ANY} requests.post.assert_called_with(requests_arguments)
quelle
@mock.patch.object(module, 'ClassName') def test_something(self, mocked): do_some_thing() args, kwargs = mocked.call_args self.assertEqual(expected_url, kwargs.get('url'))
Siehe: Call-as-Tuples
quelle
Wenn zu viele Parameter übergeben werden und nur einer überprüft werden soll,
{'slug': 'foo', 'field1': ANY, 'field2': ANY, 'field3': ANY, ' . . . }
kann es umständlich sein, so etwas zu tun .Ich habe den folgenden Ansatz gewählt, um dies zu erreichen:
args, kwargs = requests.post.call_args_list[0] self.assertTrue('slug' in kwargs, 'Slug not passed to requests.post')
In einfachen Worten, dies gibt ein Tupel mit allen Positionsargumenten und ein Wörterbuch mit allen benannten Argumenten zurück, die an den Funktionsaufruf übergeben wurden, sodass Sie jetzt alles überprüfen können, was Sie wollen.
Außerdem, wenn Sie den Datentyp einiger Felder überprüfen möchten
args, kwargs = requests.post.call_args_list[0] self.assertTrue(isinstance(kwargs['data'], dict))
Auch, wenn Sie Argumente vorbei sind (anstelle von Keyword - Argumente), können Sie auf diese zugreifen über
args
wie dieseself.assertEqual( len(args), 1, 'post called with different number of arguments than expected' )
quelle
Sie können Mock.call_args verwenden , um Argumente zu sammeln, mit denen Ihre Methode aufgerufen wurde. Wenn Ihre verspottete Methode aufgerufen wurde, würde sie Argumente zurückgeben, mit denen Ihre Methode in Form eines Tupels geordneter Argumente und Schlüsselwortargumente aufgerufen wurde.
class A(object): def a_method(self, a, b,c=None): print("Method A Called") def main_method(): # Main method instantiates a class A and call its method a = A() a.a_method("vikalp", "veer",c= "Test") # Test main method : We patch instantiation of A. with patch(__name__ + '.A') as m: ret = m.return_value ret.a_method = Mock() res = main_method() args, kwargs = ret.a_method.call_args print(args) print(kwargs)
Der obige Code gibt geordnete Argumente und Schlüsselwortargumente wie folgt aus:
('vikalp', 'veer') {'c': 'Test'}
Sie können dies wie folgt behaupten:
assert args[0] == "vikalp" assert kwargs['c'] == "Test"
quelle
Sie können Folgendes verwenden: assert_any_call (args) https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_any_call
request.post.assert_any_call (request_arguments)
quelle