问题
My understanding is that autospec
in its simplest form as used here will check the signature of the function which is being mocked against the presented arguments. Its purpose is to raise an error if they do not match. In the code below it seems to inject an additional argument - the object itself. Why does the use of the mock module's autospec
lead to the unexpected behavior shown here?
For this question I've created a simplified version in the module simplebutton
. When it's run as the main module the line, "It's no joke," is printed.
#module simplebutton
import sys
class _Dialog2:
def callback(self):
print("It's no joke")
def main():
dialog = _Dialog2()
dialog.callback()
if __name__ == '__main__':
sys.exit(main())
The test module test_simplebutton
contains two tests both of which work. Both mock the callback
function. The second test, however, includes autospec=True
.
@unittest.mock.patch('simplebutton._Dialog2.callback',
name='callback', autospec=True)
In this test the callback function which should be called with no arguments has to be called with an argument of dialog
otherwise the test fails.
Edit: Everyone knows that you don't call a method by method(instance)
but by instance.method()
. That was my mistake. Here it needs to be instance1.method('instance2')
where instance1
is the mock and instance2
is the object containing the mocked method. Thanks to Michele d'Amico for this.
mock_callback.assert_called_once_with(dialog)
The test suite follows:
#module test_simplebutton
import unittest
import unittest.mock
import simplebutton
class Test_Dialog(unittest.TestCase):
@unittest.mock.patch('simplebutton._Dialog2.callback',
name='callback')
def test_direct_call_to_callback_by_mocking_1(self, mock_callback):
dialog = simplebutton._Dialog2()
dialog.callback()
mock_callback.assert_called_once_with()
@unittest.mock.patch('simplebutton._Dialog2.callback',
name='callback', autospec=True)
def test_direct_call_to_callback_by_mocking_2(self, mock_callback):
dialog = simplebutton._Dialog2()
dialog.callback()
mock_callback.assert_called_once_with(dialog)
回答1:
By autospec=True
patch replace a object (in your case method) by a mock with same original objects's signature. Moreover the resulting mock cannot be extended: try to access to attributes or methods that are not in the original definition (or in MagicMock()
) will raise an exception.
In the first case (without autospec=True
) you are patching a bound method by an unbounded one. When you invoked your patched method, mock_callback
is called as a function and not as a bound method of dialog
object.
When you use autospec=True
in @patch
decorator it replace the original one by a new bound method mock_callback
: that is like all other bound method and will be call by self as first argument. In order to the example more clear I changed it to explain better the behavior of autospec=True
patch's parameter.
import unittest
import unittest.mock
import simplebutton
class Test_Dialog(unittest.TestCase):
@unittest.mock.patch('simplebutton._Dialog2.callback')
def test_direct_call_to_callback_by_mocking_1(self, mock_callback):
dialog = simplebutton._Dialog2()
dialog.callback()
mock_callback.assert_called_once_with()
mock_callback.reset_mock()
simplebutton._Dialog2.callback()
mock_callback.assert_called_once_with()
@unittest.mock.patch('simplebutton._Dialog2.callback', autospec=True)
def test_direct_call_to_callback_by_mocking_2(self, mock_callback):
dialog = simplebutton._Dialog2()
dialog.callback()
mock_callback.assert_called_once_with(dialog)
self.assertRaises(Exception, simplebutton._Dialog2.callback)
dialog2 = simplebutton._Dialog2()
dialog.callback()
dialog2.callback()
mock_callback.assert_has_calls([unittest.mock.call(dialog), unittest.mock.call(dialog2)])
In the first test we make either an explicit call to _Dialog2.callback()
as unbound method by simplebutton._Dialog2.callback()
and the behavior is exactly the same of dialog.callback()
.
In the second one if we try to call it as unbound like the first test it will raise an exception. Moreover if we call the method from two different objects we will found two different call to the same mock and we can identify them.
I hope is clear now what happen and what you should expect when you use autospec=True
parameter.
来源:https://stackoverflow.com/questions/27878750/mocks-autospec-injects-a-wrong-argument-into-a-called-function