Attaching a mock function to another mock breaks reset_mock()

Mark Bourne nntp.mbourne at spamgourmet.com
Mon Jun 19 16:48:12 EDT 2023


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.



More information about the Python-list mailing list