Title: Python unittest.mock.mock_calls stores references to arguments instead of their values
Type: behavior Stage:
Components: Tests Versions: Python 3.4
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: Guillaume Chorn, michael.foord, r.david.murray, rbcollins, veky
Priority: normal Keywords:

Created on 2016-09-30 17:54 by Guillaume Chorn, last changed 2016-10-04 10:07 by michael.foord. This issue is now closed.

Messages (5)
msg277764 - (view) Author: Guillaume Chorn (Guillaume Chorn) Date: 2016-09-30 17:54
In the unittest.mock library, when a Mock object stores the calls made on it in its `mock_calls` attribute, it appears to store references to the call arguments instead of the actual values of the call arguments. In cases where call args are mutable types, this results in the undesirable behavior below:

import mock

arg = ['one']


# passes

arg += ['two']


# fails, even though we just verified the first call above!

# passes, even though we didn't make the exact same call twice!
test_function.assert_has_calls([['one', 'two']),['one', 'two'])
msg277766 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2016-09-30 18:21
This is pretty much just the way python works.  The only alternative would be for mock to try to do a deepcopy, which won't always work, so it is probably better not to do it at all.
msg277975 - (view) Author: Guillaume Chorn (Guillaume Chorn) Date: 2016-10-03 18:17
If it's true that our ability to accurately deep-copy mutable args makes fixing this behavior impossible, we should at the very least update the official unittest.mock documentation to warn users that testing for mock calls with mutable arguments is not reliable; i.e. make it clear that we're storing a reference to the arguments and not just the values. If it's not a bug, it's certainly a limitation that deserves as much mention as possible.
msg278008 - (view) Author: Vedran Čačić (veky) * Date: 2016-10-04 05:34
> # passes, even though we didn't make the exact same call twice!

Weird. It looks like a matter of perspective, but to me it surely _does_ seem like you _did_ make the exact same call twice.

> test_function(arg)
> test_function(arg)

Neither test_function nor arg were rebound in the meantime. (If there's a "bug", it's a subtle thing that += seems like rebinding, and in fact it does rebind immutable objects. But that's off topic here.)

[*Also, it could be argued that assert_has_calls should compare the arguments with `is` instead of `==`, but that would probably break too many things.]

But let's try to be constructive. IIRC, unittest was modelled after Java's analogous library. Can someone check how Java solves this? I'm pretty sure it has the same "problem" (when arguments are objects), but maybe it keeps the reference semantics totally as conjectured in the previous paragraph.
msg278025 - (view) Author: Michael Foord (michael.foord) * (Python committer) Date: 2016-10-04 10:07
This is a deliberate design decision of mock. Storing references works better for the more general case, with the trade-off being that it doesn't work so well for mutable arguments.

See the note in the docs with a workaround:
Date User Action Args
2018-05-28 20:11:38r.david.murraylinkissue33667 superseder
2016-10-04 10:07:00michael.foordsetstatus: open -> closed
resolution: not a bug
messages: + msg278025
2016-10-04 05:34:03vekysetnosy: + veky
messages: + msg278008
2016-10-03 18:17:39Guillaume Chornsetmessages: + msg277975
2016-09-30 18:21:37r.david.murraysetnosy: + rbcollins, r.david.murray, michael.foord
messages: + msg277766
2016-09-30 17:54:38Guillaume Chorncreate