requests-mock
requests-mock copied to clipboard
Method to clear existing mocks?
Is it possible to clear existing mocks? If not, can you please make clear in the docs that all mocks need to be set up front. Might be a "user IQ" error but I'm having trouble and I think it is because a second request for the same resource is matching an earlier mock that I put in place (I'm testing first GET success, second GET some sort of error).
Partly sorted this. My initial issue was a misplaced decorator and once this was fixed, it was possible to add new 'expected requests' and have them responded to. It might be nice to have some way to clear the request_headers array though as I'm having to keep track if its length as it seems to grow forever.
Silly me; although the list growing might be an issue in long term scenarios, using negative indices...
# Test method of the latest mocked request
self.assert_Equal(rsp.request_history[-1].method, 'GET')
...works fine.
So I think we're thinking about where you so mocking differently. When I use the library i create a mock per test and define the responses for only that test. This makes things like request count very useful because it's only the number of request made in this test.
I could probably be persuaded to add a clear function, but to my mind the mocks are disposable so you don't really clear it you just make another one
Oh, there's also a last_request
parameter because that pattern is common: https://requests-mock.readthedocs.io/en/latest/history.html
How about not keeping a mutable when creating the decorator:
@requests_mock.Mocker()
def f1(m):
print(m.call_count)
m.get('https://www.peterbe.com', text='OK')
requests.get('https://www.peterbe.com')
f1()
f1()
This prints
0
1
But if you change it to this form:
def f2():
with requests_mock.Mocker() as m:
print(m.call_count)
m.get('https://www.peterbe.com', text='OK')
requests.get('https://www.peterbe.com')
f2()
f2()
it prints
0
0
In other words, I think using @requests_mock.Mocker()
(or @requests_mock.mock()
which appears to be the same thing) is dangerous.
It means that if you write a test like this:
@requests_mock.Mocker()
def f1(m):
m.get('https://www.peterbe.com', text='OK')
requests.get('https://www.peterbe.com')
assert m.call_count == 1, m.call_count
f1()
f1()
it will fail on AssertionError: 2
Now, it's a bit weird to call the same test function (could be a unittest.TestCase
class method) twice but it bit us really hard when we didn't realize that our pytest
accidentally ran the same file twice.
Oh really? I've never hit that problem (I'd agree it would most commonly indicate a mistake).
I think the intuitive thing here and what I'd expect is a new mock object every time the decorator runs, but decorators are kind of funny so I can see how it happened.
I can't see a backwards compatibility issue with making that change, but it'll take me a little bit of time to get to it.
Pull requests welcomed.
Weird, I took a look at while I can see the logic actually executing the snippet you provide does the right thing:
import requests
import requests_mock
@requests_mock.Mocker()
def f1(m):
print(m.call_count)
m.get('https://www.peterbe.com', text='OK')
requests.get('https://www.peterbe.com')
f1()
f1()
f1()
f1()
f1()
0
0
0
0
0
This might be something that happens when there's some test framework wrapping it up?
Taking the assumption that there is still a bug here - this may be more difficult to fix than I expected. The Mocker() (or mock) is actually an object that is handling the patching, rather than a decorator that creates and inserts a mock. Maybe the latter makes more sense in hindsight but it'd be a pain to fix.
We could in the short term make a clear()
function and call it on function entry which would at least mean you don't get left over history. It's not necessarily the right thing from an architecture perspective, but would solve the symptom.
Can you verify how you're producing that case please?
Duplicate of #21 - but there's more discussion here so continuing.
I have this Python 3.7.5 virtualenv and I copied those exact lines into a little file and ran it:
▶ cat dummy.py
import requests
import requests_mock
@requests_mock.Mocker()
def f1(m):
print(m.call_count)
m.get("https://www.peterbe.com", text="OK")
requests.get("https://www.peterbe.com")
f1()
f1()
f1()
f1()
f1()
▶ python dummy.py
0
1
2
3
4
▶ pip list | rg requests
requests 2.20.1
requests-mock 0.7.0
requests-oauthlib 0.8.0
that's a very old version of requests-mock
you have there?
(venv)[jamielennox@Jamies-MacBook-Pro-2 :) rm-multi ]$ python --version
Python 3.7.4
(venv)[jamielennox@Jamies-MacBook-Pro-2 :) rm-multi ]$ cat dummy.py
import requests
import requests_mock
@requests_mock.Mocker()
def f1(m):
print(m.call_count)
m.get("https://www.peterbe.com", text="OK")
requests.get("https://www.peterbe.com")
f1()
f1()
f1()
f1()
f1()
(venv)[jamielennox@Jamies-MacBook-Pro-2 :) rm-multi ]$ python dummy.py
0
0
0
0
0
(venv)[jamielennox@Jamies-MacBook-Pro-2 :) rm-multi ]$ pip list | grep requests
requests 2.22.0
requests-mock 1.7.0
Wow! That is old indeed. I’ll see if we can upgrade.
On Tue, Dec 3, 2019 at 6:05 PM Jamie Lennox [email protected] wrote:
that's a very old version of requests-mock you have there?
(venv)[jamielennox@Jamies-MacBook-Pro-2 :) rm-multi ]$ python --version Python 3.7.4 (venv)[jamielennox@Jamies-MacBook-Pro-2 :) rm-multi ]$ cat dummy.py import requests import requests_mock
@requests_mock.Mocker() def f1(m): print(m.call_count) m.get("https://www.peterbe.com", text="OK") requests.get("https://www.peterbe.com")
f1()f1()f1()f1()f1() (venv)[jamielennox@Jamies-MacBook-Pro-2 :) rm-multi ]$ python dummy.py 0 0 0 0 0 (venv)[jamielennox@Jamies-MacBook-Pro-2 :) rm-multi ]$ pip list | grep requests requests 2.22.0 requests-mock 1.7.0
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/jamielennox/requests-mock/issues/114?email_source=notifications&email_token=AAAGQ47JIFG62XXSUIEF6K3QW3Q3LA5CNFSM4JOERFPKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEF3D62Q#issuecomment-561397610, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAGQ43FX6XKRNSPOJLRG63QW3Q3LANCNFSM4JOERFPA .
-- Peter Bengtsson Mozilla MDN Web Docs https://www.peterbe.com
So i found your fix - and apparently it was included in 1.4.0 without any real documentation. I can only apologize for that, the newer versions have been better.
https://github.com/jamielennox/requests-mock/commit/d59b584d8806b3206c2fcb46d8469f4325626b55
So as long as you use requests_mock.Mocker()
in a "disposable" way, there's no need for a method to clear. Right?
I guess I could see a rare case where you'd want to set up the responses once and re-use it repeatedly but that'd still be possible to do in a disposable way.
I have cleared count in the past when I have a specific series of steps in a test that requires it. It is a rare case, but useful under the right circumstance. It would be more consistent with MagicMock.
with Mocker() as m:
m.get('mock://get_cacheable_data')
foo() # calls api
m.reset() # no assert done here
foo() # should not call api again
assert(m.call_count == 0)