homepage

This issue tracker has been migrated to GitHub , and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Add restricted mocks to the python unittest mocking framework
Type: enhancement Stage: resolved
Components: Library (Lib) Versions: Python 3.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: berker.peksag, ericvw, grzgrzgrz3, lkollar, mariocj89, michael.foord, rbcollins, vstinner
Priority: normal Keywords:

Created on 2017年06月01日 20:16 by mariocj89, last changed 2022年04月11日 14:58 by admin. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 1923 merged mariocj89, 2017年06月02日 19:49
PR 5107 merged p-ganssle, 2018年01月05日 17:42
Messages (11)
msg294961 - (view) Author: Mario Corchero (mariocj89) * (Python triager) Date: 2017年06月01日 20:16
Define a way to disable the automatic generation of submocks when accessing an attribute of a mock which is not set.
Rationale:
Inspired by GMock RestrictedMock, it aims to allow the developer to declare a narrow interface to the mock that defines what the mocks allows to be called on.
The feature of mocks returning mocks by default is extremely useful but not always desired. Quite often you rely on it only at the time you are writing the test but you want it to be disabled at the time the mock is passed into your code.
It also prevents user errors when mocking incorrect paths or having typos when calling attributes/methods of the mock.
We have tried it internally in our company and it gives quite a nicer user experience for many use cases, specially for new users of mock as it helps out when you mock the wrong path.
Posible interfaces:
New Mock type, SeledMock which can be used instead of the "common" mock that has an attribute "sealed" which once set to true disables the dynamic generation of "submocks"
The final goal is to be able to write tests like:
>>> m = mock.Mock() # or = mock.SealableMock()
>>> m.method1.return_value.attr1.method2.return_value = 1
>>> mock.seal(m) # or mock.sealed = True
>>> m.method1().attr1.method2() # This path has been declared above
# 1
>>> m.method1().attr2 # This was not defined so it is going to raise a meaningful exception
# Exception: SealedMockAttributeAccess: mock.method1().attr2
msg294969 - (view) Author: Mario Corchero (mariocj89) * (Python triager) Date: 2017年06月01日 21:00
Sample implementation using the new class:
https://github.com/mariocj89/cpython/commit/2f13963159e239de041cd68273b9fc4a2aa778cd
Sample implementation using the new function to seal existing mocks:
https://github.com/mariocj89/cpython/commit/9ba039e3996f4bf357d4827123e0b570d84f5bb6
Happy to submit a PR if the idea is accepted.
The only benefit I see from the using a separate class is that you will be able to do:
>>> m = mock.SealedMock()
>>> m.important_attr = 42
>>> m.freeflow_attribute = mock.Mock()
>>> mock.seal(m)
which will allow to define "subparts of the mock" without the sealing.
That said I still prefer the function implementation as it looks much nicer (credit to Victor Stinner for the idea)
msg296159 - (view) Author: Berker Peksag (berker.peksag) * (Python committer) Date: 2017年06月16日 05:04
I personally never need this feature before so I will add Michael and Robert to nosy list to take their opinions.
msg296573 - (view) Author: Grzegorz Grzywacz (grzgrzgrz3) * Date: 2017年06月21日 18:45
Existing mock implementation already has that feature. Mock attributes can be limited with `spec` attribute.
>>> inner_m = Mock(spec=["method2"], **{"method2.return_value": 1})
>>> m = Mock(spec=["method1"], **{"method1.return_value": inner_m})
>>> 
>>> m.method1().method2()
1
>>> 
>>> m.method1().attr
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "/usr/lib/python3.5/unittest/mock.py", line 580, in __getattr__
 raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'attr'
msg296589 - (view) Author: Mario Corchero (mariocj89) * (Python triager) Date: 2017年06月21日 22:15
Whilst I agree that using spec can be used for a similar purpose and I did not know about being able to do nested definitions via the arguments (the **{"method1.return_value": 1}, really cool!) I find the idea of allowing users to halt the mock generation really useful. It is much less disruptive and feels more natural.
Compare:
>>> inner_m = Mock(spec=["method2"], **{"method2.return_value": 1})
>>> m = Mock(spec=["method1"], **{"method1.return_value": inner_m})
with: 
>>> m = mock.Mock()
>>> m.method1().method2() = 1
>>> mock.seal(m)
In brief, seal allows users to just add the method to their existing workflow where they use generic mocks. Moreover, it is extremely user friendly, many of the developers that struggle with the mocking module found seal really helpful.
msg296600 - (view) Author: Michael Foord (michael.foord) * (Python committer) Date: 2017年06月21日 23:22
I don't see what this buys over spec and autospec. I'd be inclined to close it without a compelling use case beyond what is already supported.
msg296609 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017年06月22日 00:09
> I don't see what this buys over spec and autospec. I'd be inclined to close it without a compelling use case beyond what is already supported.
I proposed to Mario to open an issue since I like his API. Even if "sealing" mocks is unlikely to be the most common case, when you need it, I prefer his API over the specs thing which reminds me bad time with mox. I prefer the declarative Python-like API, rather than Mock(spec=["method2"], **{"method2.return_value": 1}).
But yeah, technically specs and sealing seems similar. It's just another way to describe a mock. Since I prefer sealing, I would like to allow users to choose between specs and sealing.
msg296697 - (view) Author: Michael Foord (michael.foord) * (Python committer) Date: 2017年06月23日 10:50
Note that you can use an object as the parameter to the spec argument rather than just a list of attributes. 
Hmmm... I'm not totally opposed to the addition of a "seal_mock" method (optionally with a recurse boolean for child mocks) being added to the Mock/MagicMock classes. It's quite a nice API. I don't like the idea of an additional Mock class for this.
msg296705 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017年06月23日 12:34
> I don't like the idea of an additional Mock class for this.
Hum, in the current implementation, it's an enhancement of the Mock class, no more a new class.
msg301230 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017年09月04日 17:33
I will merge the PR this week, the PR now LGTM.
msg304498 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017年10月17日 11:35
New changeset 552be9d7e64f91b8e4ba5b29cd5dcc442d56f92c by Victor Stinner (Mario Corchero) in branch 'master':
bpo-30541: Add new method to seal mocks (GH61923)
https://github.com/python/cpython/commit/552be9d7e64f91b8e4ba5b29cd5dcc442d56f92c
History
Date User Action Args
2022年04月11日 14:58:47adminsetgithub: 74726
2018年01月05日 17:42:54p-gansslesetpull_requests: + pull_request4974
2017年10月17日 11:36:43vstinnersetstatus: open -> closed
resolution: fixed
stage: patch review -> resolved
2017年10月17日 11:35:15vstinnersetmessages: + msg304498
2017年09月04日 17:33:16vstinnersetmessages: + msg301230
2017年06月24日 08:05:21lkollarsetnosy: + lkollar
2017年06月23日 12:34:02vstinnersetmessages: + msg296705
2017年06月23日 10:50:21michael.foordsetmessages: + msg296697
2017年06月22日 00:09:25vstinnersetmessages: + msg296609
2017年06月21日 23:22:55michael.foordsetmessages: + msg296600
2017年06月21日 22:15:41mariocj89setmessages: + msg296589
2017年06月21日 18:45:28grzgrzgrz3setnosy: + grzgrzgrz3
messages: + msg296573
2017年06月16日 05:04:36berker.peksagsetnosy: + rbcollins, berker.peksag, michael.foord

messages: + msg296159
stage: patch review
2017年06月02日 19:49:19mariocj89setpull_requests: + pull_request2003
2017年06月01日 21:49:56ericvwsetnosy: + ericvw
2017年06月01日 21:00:59mariocj89setmessages: + msg294969
2017年06月01日 20:16:56mariocj89create

AltStyle によって変換されたページ (->オリジナル) /