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

Reply via email to