Repository navigation
Allow waiting on a mock #61215
Copy link
Copy link
Closed
Labels
3.13only security fixesonly security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement
Description
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee = None
closed_at = None
created_at = <Date 2013-01-22.12:15:47.799>
labels = ['type-feature', 'library', '3.9']
title = 'Allow waiting on a mock'
updated_at = <Date 2020-06-10.14:37:15.926>
user = 'https://github.com/pitrou'bugs.python.org fields:
activity = <Date 2020-06-10.14:37:15.926>
actor = 'Kentzo'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Library (Lib)']
creation = <Date 2013-01-22.12:15:47.799>
creator = 'pitrou'
dependencies = []
files = []
hgrepos = []
issue_num = 17013
keywords = ['patch']
message_count = 18.0
messages = ['180377', '181398', '247777', '340146', '340153', '340158', '340164', '352211', '356898', '367446', '371133', '371165', '371175', '371193', '371195', '371196', '371202', '371207']
nosy_count = 13.0
nosy_names = ['jcea', 'pitrou', 'rbcollins', 'cjw296', 'ezio.melotti', 'michael.foord', 'Ilya.Kulakov', 'lisroach', 'mariocj89', 'lkollar', 'pablogsal', 'Kentzo', 'xtreak']
pr_nums = ['12818', '16094', '17133', '20759']
priority = 'normal'
resolution = None
stage = 'patch review'
status = 'open'
superseder = None
type = 'enhancement'
url = 'https://bugs.python.org/issue17013'
versions = ['Python 3.9']Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.13only security fixesonly security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement
Projects
- StatusShow more project fieldsDone
Activity
pitrou commentedon Jan 22, 2013
In non-trivial tests, you may want to wait for a method to be called in another thread. This is a case where unittest.mock currently doesn't help. It would be nice to be able to write:
And perhaps
(with an optional timeout?)
If we don't want every Mock object to embed a threading.Event, perhaps there could be a ThreadedMock subclass?
Or perhaps even:
so that people can pass multiprocessing.Event if they want to wait on the mock from another process?
voidspace commentedon Feb 5, 2013
There is a similar feature request on the mock issue tracker: http://code.google.com/p/mock/issues/detail?id=189
I prefer this proposal to the other one though. (Although technically allowing a wait for multiple calls is more flexible.)
rbtcollins commentedon Jul 31, 2015
Now at testing-cabal/mock#189
tirkarthi commentedon Apr 13, 2019
Is there still sufficient interest in this? I gave an initial try at this based on my limited knowledge of mock and Antoine's idea. I created a new class WaitableMock that inherits from Mock and accepts an event_class with threading.Event as default and stores an event object. When call is made then via CallableMixin _mock_call is called which I have overridden to set the event object. I have wait_until_called that waits on this event object for a given timeout to return whether it's called or not.
I am not sure of implementing wait_until_called_with since I set the event object for any call in _mock_call irrespective of argument and since the call params for which the event has to be set are passed to wait_until_called_with. Perhaps allow passing a call object to wait_until_called_with and and during _mock_call set event object only if the call is the same as one passed to wait_until_called_with ?
See also bpo-26467 to support asyncio with mock
Initial implementation
Sample program :
# foo is called but waiting for call to bar times out and hence no calls to bar are registered though bar is eventually called in the end and the call is registered at the end of the program.
➜ cpython git:(master) ✗ time ./python.exe ../backups/bpo17013_mock.py
foo called True
bar called False
./python.exe ../backups/bpo17013_mock.py 0.40s user 0.05s system 7% cpu 5.765 total
mariocj89 commentedon Apr 13, 2019
I think this is REALLY interesting!, there are many situations where this has been useful, it would greatly improve multithreading testing in Python.
Q? I see you inherit from Mock, should it inherit from MagicMock?
I'd say send the PR and the discussion can happen there about the implementation, seems there is consensus in this thread.
I would find it OK to start with
wait_until_calledif you want and can be discussed in another issue/PR if you don't have the time/think it might not be worth.tirkarthi commentedon Apr 13, 2019
Thanks Mario for the feedback.
yes, it can inherit from MagicMock to mock magic methods and to wait on their call.
I thought some more about waiting for function call with arguments. One idea would be to have a dictionary with args to function as key mapping to an event object and to set and wait on that event object. To have wait_until_called that is always listening on a per mock object and to have wait_until_called_with listening on event object specific to that argument. Below is a sample implementation and an example to show it working with wait_until_called and wait_until_called_with. wait_until_called_with is something difficult to model since it needs per call event object and also need to store relevant key mapping to event object that is currently args and doesn't support keyword arguments. But wait_until_called is little simpler waiting on a per mock event object.
I will open up a PR with some tests.
# Sample program to wait on arguments, magic methods and validating wraps
mariocj89 commentedon Apr 13, 2019
Kooning great! I would Add a test for the following (I think both fails with the proposed impl):
Also, I don’t have a great solution for it but it might be worth prefixing time-out with something in the wait_untill_called_with. In situations where the mocked object has a timeout parameter (which is a common argument for multithreaded apps).
mariocj89 commentedon Sep 12, 2019
Spoke offline with @XTreak, I'll be sending a PR for this to take over the existing one.
@lisroach proposed a new name, EventMock to differentiate it from any awaitable async notation.
@michael.foord suggested using
mock_timeoutas the argument.Discussed also to change the naming of the method to
await_until_any_callto make the semantics similar to the one thatMockprovides. Asawait_until_called_withmight mislead the user that it applies only to the last call.Kentzo commentedon Nov 18, 2019
I have submitted an alternative implementation of this feature heavily inspired by _AwaitEvent I wrote for asynctest [0].
There was recently an interest from the community towards asynctest to the point that got some of its measures merged into CPython [1].
The key features of my implementation [2]:
called)Accepting this PR will allow me to bring _AwaitEvent thereby completing mock.py with waiting mechanics with identical semantics for both threading-based and asyncio-based cases.
0: https://github.com/Martiusweb/asynctest/blob/4b1284d6bab1ae90a6e8d88b882413ebbb7e5dce/asynctest/mock.py#L428
1: #9296
2: #17133
mariocj89 commentedon Apr 27, 2020
For the record, I have no strong preference over either implementation. @voidspace preferred offline the new mock class, but sadly the rationale is lost in the physical word.
6 remaining items
Kentzo commentedon Jun 10, 2020
As far as I understand it introduces 3 methods that may clash. It's unlikely but (I speculate)
is still more likely than an identity check with called.
That being said, the PR can be redone as a subclass. But that implementation will not play
as nicely with the planned
awaitedproperty for asyncio mocks (see description in the PR).Kentzo commentedon Jun 10, 2020
You're right, just never used it as I never needed an identity check against True / False
The PR is re-done to use an additional property call_event instead of extending called for backwards compatibility.
gh-61215: New mock to wait for multi-threaded events to happen (#16094)
Merge branch 'main' into modulelocals
gh-61215: Rename `wait_until_any_call` to `wait_until_any_call_with` (#…
Merge branch 'main' into modulelocals
GH-61215: threadingmock: Remove unused branch for `timeout` (#106591)
gh-61215: threadingmock: Improve test suite to avoid race conditions (#…
terryjreedy commentedon Feb 24, 2025
The 5 attached PRs seem to have already gone beyond the original issue. Let's open a new issue for anything else.