Mailing List Archive

Attaching a mock function to another mock breaks reset_mock()
I've came across an issue with attaching a mock function to another mock
object. It looks like this might be a bug in unittest.mock, but it's
possible I'm misunderstanding or doing something wrong.

I'm currently using Python 3.8.10, which is the default installed on
Ubuntu 20.04. I don't have time to install and try other versions right
now but from a quick look at the source at
<https://github.com/python/cpython/blob/main/Lib/unittest/mock.py>, it
looks like the same issue would still occur with the latest. I have a
workaround, but would still like to know if I'm doing something wrong
which I can correct, or should report this as a bug.

The class I'm testing (let's call it A) is given an instance of another
class (let's call it B). In normal operation, some other code adds a
callback function as an attribute to B before passing it to A, and A
calls that callback at certain points. I agree this isn't particularly
nice; I didn't write the code and don't have time to sort out all the
structural issues at the moment.

So, in setting up the mock B, I need to attach a mock callback function
to it. I'd like the mock function to raise an exception if it's called
with the wrong arguments. I can create such a mock function with, for
example:
```
mock_callback = mock.create_autospec(lambda a, b, c: None)
```
Calls like mock_callback(1,2,3) or mock_callback(a=1,b=2,c=3) are fine,
and the test can later check the exact values passed in, while
mock_callback(1,2) or mock_callback(1,2,3,x=9) raise an exception at the
time of the call - exactly what I want.

However, when I attach this mock callback to the mock instance of B, the
reset_mock() method breaks. For example, using object in place of B,
since the rest of its implementation is irrelevant to the issue:
```
mock_callback = mock.create_autospec(lambda a, b, c: None)
mock_b = mock.create_autospec(object, spec_set=False, instance=True)
mock_b.attach_mock(mock_callback, 'some_callback')
mock_b.reset_mock()
```

The call to reset_mock() results in:
```
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.8/unittest/mock.py", line 603, in reset_mock
child.reset_mock(visited)
TypeError: reset_mock() takes 0 positional arguments but 1 was given
```

This seems to occur because, when creating a mock of a function or
method, create_autospec calls _setup_func (via _set_signature), which
sets the mock callback's reset_mock method to a function which doesn't
accept any arguments. When mock_b.reset_mock() is called, that
recursively calls reset_mock() on all the child mocks (including the
mock callback), passing a number of arguments - which causes the above
exception.

I thought I might be able to just assign the mock callback to an
attribute of the mock B, rather than using attach_mock, and avoid the
recursive call to its reset_mock():
```
mock_b.some_callback = mock_callback
```
But mock_b.reset_mock() still raises that exception. I think some magic
in the mocks automatically attaches mock_callback as a child of mock_b
even in that case.

My current workaround is to just use
```
mock_callback = mock.Mock(spec_set=lambda: None)
```
instead. While using spec_set there prevents incorrect use of things
like mock_b.some_callback.random_attribute, it doesn't enforce the
arguments that the function can be called with, even if I do include
them in the lambda (as in the first example).

Is there something I'm doing wrong here? Or does this seem like a bug
in unittest.mock that I should report? Perhaps this is something that's
not often done, so the issue hasn't been noticed before. Trying to
search for information generally leads back to the unittest.mock
documentation, and general tutorials on using create_autospec,
attach_mock, etc. without anything specific about this case.

--
Mark.

--
https://mail.python.org/mailman/listinfo/python-list