def test_can_wrap_with_lambda(self): """Test that lambda functions can be used as the callbacks.""" class Dummy: def instance(self, mock): mock(self, "method") wrap_method(Dummy, "instance", after=lambda self, mock: mock(self, "after")) with self.subTest("from instance"): mock = unittest.mock.Mock() source = Dummy() caller = source.instance mock.assert_not_called() caller(mock) mock.assert_has_calls( [ unittest.mock.call(source, "method"), unittest.mock.call(source, "after") ], any_order=False, ) with self.subTest("from type"): mock = unittest.mock.Mock() caller = Dummy.instance mock.assert_not_called() caller("this", mock) mock.assert_has_calls( [ unittest.mock.call("this", "method"), unittest.mock.call("this", "after") ], any_order=False, )
def test_raises_on_invalid_name(self): """Test that a suitable error is raised if the method doesn't exist.""" class Dummy: pass with self.assertRaisesRegex( ValueError, "Method 'bad' is not defined for class 'Dummy'"): wrap_method(Dummy, "bad", before=lambda self: None)
def test_docstring_inherited(self): """Test that the docstring of a method is correctly passed through, to avoid clobbering documentation.""" class Dummy: def method(self): """This is documentation.""" wrap_method(Dummy, "method", before=lambda self: None) self.assertEqual(Dummy.method.__doc__, "This is documentation.")
def test_wrapping_object___init_subclass__(self): """Test that wrapping the magic ``__init_subclass__`` method works. This method is implicitly made into a class method without needing a decorator.""" class Dummy: pass mock = unittest.mock.Mock() wrap_method(Dummy, "__init_subclass__", before=mock) class Child(Dummy): pass mock.assert_called_once_with(Child)
def test_wrapping_object___new__(self): """Test that wrapping the magic __new__ method works when it is inherited. This is a very special case, because by inheritance ``A.__new__`` is ``type.__new__``, but that's not we want to wrap; we need to have used ``type.__getattribute__`` to make sure that we're getting the default implementation ``object.__new__``, and not the ``__new__`` method that literally constructs new types. """ class Dummy: pass mock = unittest.mock.Mock() wrap_method(Dummy, "__new__", before=mock) dummy = Dummy() mock.assert_called_once_with(Dummy)
def test_wrapping_object___eq__(self): """Test that wrapping equality works. ``type`` also implements ``__eq__`` in a way that returns ``NotImplemented`` if one of the operands is not a ``type``, so this tests that we are successfully finding ``object.__eq__`` in the resolution of the wrapped method.""" class Dummy: def __init__(self, n): self.n = n def __eq__(self, other): return self.n == other.n mock = unittest.mock.Mock() wrap_method(Dummy, "__eq__", before=mock) left = Dummy(1) right = Dummy(2) self.assertNotEqual(left, right) mock.assert_has_calls([unittest.mock.call(left, right)])
def test_can_wrap_with_mock(self): """This is kind of a meta test, to check that we can use a ``unittest.mock.Mock`` instance as the callback.""" class Dummy: def instance(self, x): pass mock = unittest.mock.Mock() wrap_method(Dummy, "instance", before=mock) with self.subTest("from instance"): mock.reset_mock() source = Dummy() source.instance("hello, world") mock.assert_called_once_with(source, "hello, world") with self.subTest("from type"): mock.reset_mock() Dummy.instance("this", "hello, world") mock.assert_called_once_with("this", "hello, world")
def test_can_wrap_with_callable_class(self): """Test that a class with a ``__call__`` but no descriptor protocol can be used as the callbacks.""" class Dummy: def instance(self, mock): mock(self, "method") class Callback: # Note that this class does not implement the descriptor protocol, unlike normal Python # functions. ``__call__`` itself implements ``__get__``, but that just bounds the # ``Callback`` instance to ``self``. def __call__(self, this, mock): mock(this, "after") wrap_method(Dummy, "instance", after=Callback()) with self.subTest("from instance"): mock = unittest.mock.Mock() source = Dummy() caller = source.instance mock.assert_not_called() caller(mock) mock.assert_has_calls( [ unittest.mock.call(source, "method"), unittest.mock.call(source, "after") ], any_order=False, ) with self.subTest("from type"): mock = unittest.mock.Mock() caller = Dummy.instance mock.assert_not_called() caller("this", mock) mock.assert_has_calls( [ unittest.mock.call("this", "method"), unittest.mock.call("this", "after") ], any_order=False, )
def test_wrapping___init__(self): """Test that wrapping the magic __init__ method works.""" class Dummy: def __init__(self, mock): mock("__init__") self.mock = mock def add_extra_property(self, _): mock("extra") self.extra = "hello, world" wrap_method(Dummy, "__init__", after=add_extra_property) mock = unittest.mock.Mock() dummy = Dummy(mock) self.assertIs(dummy.mock, mock) self.assertEqual(dummy.extra, "hello, world") # pylint: disable=no-member mock.assert_has_calls( [unittest.mock.call("__init__"), unittest.mock.call("extra")], any_order=False, )
def test_wrapping_inherited_method(self): """Test that ``wrap_method`` will correctly find a method defined only on a parent class.""" class Parent: def instance(self, mock): mock(self, "method") class Child(Parent): pass wrap_method(Child, "instance", before=call_second_argument_with("before")) with self.subTest("from instance"): mock = unittest.mock.Mock() source = Child() caller = source.instance mock.assert_not_called() caller(mock) mock.assert_has_calls( [ unittest.mock.call(source, "before"), unittest.mock.call(source, "method") ], any_order=False, ) with self.subTest("from type"): mock = unittest.mock.Mock() caller = Child.instance mock.assert_not_called() caller("this", mock) mock.assert_has_calls( [ unittest.mock.call("this", "before"), unittest.mock.call("this", "method") ], any_order=False, )
def test_wrapping___new__(self): """Test that wrapping the magic __new__ method works. This method is implicitly made into a static method with no decorator, but still gets called with the class in the first position. Note that ``type`` implements ``__new__``, so the getter needs to ensure that it doesn't accidentally""" class Dummy: def __new__(cls, mock): mock(cls, "__new__") return super().__new__(cls) wrap_method(Dummy, "__new__", before=call_second_argument_with("extra")) mock = unittest.mock.Mock() dummy = Dummy(mock) mock.assert_has_calls( [ unittest.mock.call(Dummy, "extra"), unittest.mock.call(Dummy, "__new__") ], any_order=False, )
def test_wrapping___add__(self): """Test that wrapping an arithmetic operator works. There is nothing particularly special about ``__add__`` that (say) ``__init__`` doesn't also do, but this is just a further check that the magic methods can work. Note that ``__add__`` must be defined on the type; all magic methods ignore re-definitions in instance dictionaries.""" mock = unittest.mock.Mock() class Dummy: def __init__(self, n): self.n = n def __add__(self, other): return type(self)(self.n + other.n) wrap_method(Dummy, "__add__", before=mock) left = Dummy(1) right = Dummy(2) out = left + right self.assertIsInstance(out, Dummy) self.assertEqual(out.n, 3) mock.assert_has_calls([unittest.mock.call(left, right)])
def test_can_wrap_with_builtin(self): """Test that builtin functions can be used a callback. Many CPython builtins don't implement the desriptor protocol that all functions defined with ``def`` or ``lambda`` do, which means we need to take special care that they work. This is most relevant for C-extension functions created via pybind11, Cython or similar, rather than actual Python builtins.""" class Dummy: def instance(self, mock): mock(self, "method") # We don't want to add additional compilation requirements for one simple test in the suite, # so instead we need a CPython builtin function (of type ``types.BuiltinFunctionType``) that # has side effects when being called with an arbitrary object as the first positional # argument. ``breakpoint`` is a good choice, because it's designed to support dependency # injection with arbitrary arguments; it calls out to the overridable ``sys.breakpointhook`` # with all its arguments. We can't use ``eval`` to test the bound-method calls because the # first argument would be the instance, not the string with a side-effect-y programme. with unittest.mock.patch.object(sys, "breakpointhook") as mock: wrap_method(Dummy, "instance", before=breakpoint) with self.subTest("from instance"): mock.reset_mock() source = Dummy() source.instance(mock) mock.assert_has_calls([ unittest.mock.call(source, mock), unittest.mock.call(source, "method"), ]) with self.subTest("from type"): mock.reset_mock() Dummy.instance("this", mock) mock.assert_has_calls([ unittest.mock.call("this", mock), unittest.mock.call("this", "method"), ])
def test_wrapping_already_wrapped_method(self): """Test that a chain of wrapped methods evaluate correctly, in the right order. Methods that are explicitly overridden in child class definitions do not create chains of wrapped methods in the same way, even if they call ``super().method`` because the actual object in the child definition would be a regular function. This tests the case that method we're wrapping is the exact output of a previous wrapping.""" class Grandparent: def method(self, mock): mock(self, "grandparent") wrap_method( Grandparent, "method", before=call_second_argument_with("before 1"), after=call_second_argument_with("after 1"), ) class Parent(Grandparent): pass wrap_method( Parent, "method", before=call_second_argument_with("before 2"), after=call_second_argument_with("after 2"), ) class Child(Parent): pass wrap_method( Child, "method", before=call_second_argument_with("before 3"), after=call_second_argument_with("after 3"), ) mock = unittest.mock.Mock() child = Child() child.method(mock) mock.assert_has_calls( [ unittest.mock.call(child, "before 3"), unittest.mock.call(child, "before 2"), unittest.mock.call(child, "before 1"), unittest.mock.call(child, "grandparent"), unittest.mock.call(child, "after 1"), unittest.mock.call(child, "after 2"), unittest.mock.call(child, "after 3"), ], any_order=False, )
def test_called_with(self): """Test the basic call patterns are correct. We use regular Python functions rather than mocks to make the instances and callbacks in this simplest case, because the low-level descriptor use means that there might be side-effects to binding mocks directly.""" class Dummy: def instance(self, mock): mock(self, "method") @classmethod def class_(cls, mock): mock(cls, "method") @staticmethod def static(mock): mock("method") wrap_method( Dummy, "instance", before=call_second_argument_with("before"), after=call_second_argument_with("after"), ) wrap_method( Dummy, "class_", before=call_second_argument_with("before"), after=call_second_argument_with("after"), ) wrap_method( Dummy, "static", before=call_first_argument_with("before"), after=call_first_argument_with("after"), ) with self.subTest("from instance"): source = Dummy() with self.subTest("instance"): mock = unittest.mock.Mock() # Test that the before/after are not called when the method is accessed. caller = source.instance mock.assert_not_called() caller(mock) mock.assert_has_calls( [ unittest.mock.call(source, "before"), unittest.mock.call(source, "method"), unittest.mock.call(source, "after"), ], any_order=False, ) with self.subTest("class"): mock = unittest.mock.Mock() caller = source.class_ mock.assert_not_called() caller(mock) mock.assert_has_calls( [ unittest.mock.call(Dummy, "before"), unittest.mock.call(Dummy, "method"), unittest.mock.call(Dummy, "after"), ], any_order=False, ) with self.subTest("static"): mock = unittest.mock.Mock() caller = source.static mock.assert_not_called() caller(mock) mock.assert_has_calls( [ unittest.mock.call("before"), unittest.mock.call("method"), unittest.mock.call("after"), ], any_order=False, ) with self.subTest("from type"): with self.subTest("instance"): mock = unittest.mock.Mock() # Test that the before/after are not called when the method is accessed. caller = Dummy.instance mock.assert_not_called() caller("this", mock) mock.assert_has_calls( [ unittest.mock.call("this", "before"), unittest.mock.call("this", "method"), unittest.mock.call("this", "after"), ], any_order=False, ) with self.subTest("class"): mock = unittest.mock.Mock() caller = Dummy.class_ mock.assert_not_called() caller(mock) mock.assert_has_calls( [ unittest.mock.call(Dummy, "before"), unittest.mock.call(Dummy, "method"), unittest.mock.call(Dummy, "after"), ], any_order=False, ) with self.subTest("static"): mock = unittest.mock.Mock() caller = Dummy.static mock.assert_not_called() caller(mock) mock.assert_has_calls( [ unittest.mock.call("before"), unittest.mock.call("method"), unittest.mock.call("after"), ], any_order=False, )