kevin1024 / vcrpy

Automatically mock your HTTP interactions to simplify and speed up testing
MIT License
2.69k stars 387 forks source link

Httpx stub cause tests to breaks trying to access httpx.Response elapsed time property #600

Open isaquealves opened 3 years ago

isaquealves commented 3 years ago

When building an httpx response at vcr.stubs.httpx_stubs._from_serialized_response, the elapsed property was not covered. This cause tests to break as the elapsed property is dynamic calculated and depends on the patched method close.

Example:

# script.py
import httpx

def get_elapsed_microsecs():
    response = httpx.get('https://api.github.com/events')
    return round(response.elapsed.total_seconds() * 1000, 2)

# test_script.py
import pytest
import vcr

@vcr.use_cassete("cassete.yml")
@pytest.mark.asyncio
def test_get_elapsed_microsecs():
  ''' Breaks raising ".elapsed may ony be accessed after the response has been read or closed" '''
    microsecs = get_elapsed_microsecs()
    assert microsecs >= 0.02
parkerhancock commented 9 months ago

I suggest we close this one in view of #784

pbasista commented 5 months ago

Unfortunately, even the latest release v6.0.1 of vcrpy does not seem to resolve this issue. From my experiments it looks like httpx.Response.elapsed still cannot be accessed in tests.

As was mentioned in the original message by @isaquealves from almost 3 years ago, the problem is that the httpx.Response.elapsed property is only being set by the httpx.Response.close method which is being mocked while creating a response from serialized data in vcr.stubs.httpx_stubs._from_serialized_response function. And since there is no additional patching done by vcrpy, this property remains unavailable.

A naive fix might look similar to this:

diff --git a/vcr/stubs/httpx_stubs.py b/vcr/stubs/httpx_stubs.py
index 759cb72..b9fd59f 100644
--- a/vcr/stubs/httpx_stubs.py
+++ b/vcr/stubs/httpx_stubs.py
@@ -1,4 +1,5 @@
 import asyncio
+from datetime import timedelta
 import functools
 import inspect
 import logging
@@ -100,6 +101,7 @@ def _from_serialized_response(request, serialized_response, history=None):
         history=history or [],
         extensions=extensions,
     )
+    response.elapsed = timedelta(seconds=1)

     return response

Perhaps until a proper approach is found, this might be an acceptable work-around.