Reputation: 13518
I have the following toy class:
class MyClass:
def __init__(self, x):
self.x = x
def get_operator(self):
answer = input("Multiply? ")
if answer == "y":
return "multiply"
def multiply(self, y):
if self.get_operator() == "multiply":
return self.x * y
The following test (using pytest) will throw an error:
def test_multiply_is_called(mocker):
multiply = mocker.patch("package.module.MyClass.multiply", return_value=1, autospec=True)
my_instance = MyClass(2)
my_instance.multiply(3)
multiply.assert_called_once() # no error whatever autospec is equal to (True or False)
multiply.assert_called_with(3) # no error only if autospec=False
TypeError: Can't use 'autospec' with create=True
It gets even less clear when trying to mock Python built-in function input
:
def test_input_is_called_once(mocker):
input = mocker.patch("package.module.input", return_value="y", autospec=True)
my_instance = MyClass(2)
my_instance.get_operator()
input.assert_called_once() # no error only if autospec=False
E AssertionError: expected call not found.
E Expected: multiply(3)
E Actual: multiply(<package.module.MyClass object at 0x0000022A4BEEFD00>, 3)
I get that mocking with autospec=True
is a recommended practice, but, obviously, I have a wrong understanding of how it works, despite having read this post and this one, for instance.
Could someone please clarify this subject?
Upvotes: 1
Views: 1525
Reputation: 16855
These are actually two unrelated problems.
The first problem stems from the fact that with autospec=False
, the call my_instance.multiply(3)
just boils down to a call of the mock with the argument 3 without any checks (e.g. a simple function call).
If you use autospec=True
, however, the method multiply
is bound to my_instance
as in the real call, and called as a method, with my_instance
as the first (self
) argument. So to get it to work you need:
def test_multiply_is_called(mocker):
multiply = mocker.patch("package.module.MyClass.multiply", return_value=1, autospec=True)
my_instance = MyClass(2)
my_instance.multiply(3)
multiply.assert_called_with(my_instance, 3)
The second case is different, and I have to admit that I'm not completely clear how it works correctly with autospec=False
. The problem is that input
is not bound to the module, but it is a global import (from builtins
), and so it is created as a new mock object without the knowledge of the actual built-in function. In this case, autospec
will not work because of missing information. This can be fixed by mocking the built-in function instead:
def test_input_is_called_once(mocker):
input = mocker.patch("builtins.input", return_value="y", autospec=True)
my_instance = MyClass(2)
my_instance.get_operator()
input.assert_called_once()
This works for both autospec=True
and autospec=False
. As far as I understand, with autospec=False
it works because a mock is created for a local input
function, and that is then actually used in the call. From my point of view, the correct way for mocking input
is to mock builtins.input
, anyway.
Upvotes: 1