requests-mock icon indicating copy to clipboard operation
requests-mock copied to clipboard

Method to clear existing mocks?

Open papadeltasierra opened this issue 5 years ago • 14 comments

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).

papadeltasierra avatar Nov 16 '19 10:11 papadeltasierra

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.

papadeltasierra avatar Nov 16 '19 11:11 papadeltasierra

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.

papadeltasierra avatar Nov 16 '19 13:11 papadeltasierra

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

jamielennox avatar Nov 21 '19 22:11 jamielennox

Oh, there's also a last_request parameter because that pattern is common: https://requests-mock.readthedocs.io/en/latest/history.html

jamielennox avatar Nov 21 '19 22:11 jamielennox

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.

peterbe avatar Nov 22 '19 18:11 peterbe

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.

jamielennox avatar Nov 23 '19 21:11 jamielennox

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?

jamielennox avatar Dec 02 '19 20:12 jamielennox

Duplicate of #21 - but there's more discussion here so continuing.

jamielennox avatar Dec 02 '19 20:12 jamielennox

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

peterbe avatar Dec 03 '19 19:12 peterbe

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

jamielennox avatar Dec 03 '19 23:12 jamielennox

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

peterbe avatar Dec 03 '19 23:12 peterbe

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

jamielennox avatar Dec 03 '19 23:12 jamielennox

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.

peterbe avatar Dec 04 '19 03:12 peterbe

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)

rfportilla avatar Apr 12 '20 04:04 rfportilla