❓How it works?
pytest-remote-response works on top of interceptors specific for each library.
In a nutshell, interceptors wrap the library calls, thereby attaching additional mock/capture logic.
Note
For some nerds, interceptors uses MonkeyPatch
to monkey patch the original library with a wrapped one.
This approach certainly has some benefits and limitations; for instance, it’s surprisingly easy to write new interceptors as it’s only a wrapper method but it’s only applicable when that method is called explicitly;
for countering this, pytest-remote-response ships with two more deep interceptors _urllib
and _urllib3
.
Warning
_urllib
and _urllib3
are more low-level but are plagued with threading issues; use them carefully!
🕸️Interceptor
Note
Any custom intercept must have at-least install
and uninstall
methods and should return a BaseMockResponse
Example
from functools import warps
# Import `response` instance to get access to :class:`~pytest.MonkeyPatch` and
# :class:`~pytest_response.database.ResponseDB`
from pytest_response import response
# Library wrapper
def wrapper(func):
@wraps(func)
def inner_func(url, *args, **kwargs):
# Check if response is `True`, if `True` spoof the request from the database
if response.response:
status, data, headers = response.get(url=url)
return MockResponse(status, data, headers)
# Actual library call
lib_response = func(url, *args, **kwargs)
# Check if capture is `True`, if `True` then extract and dump the response into the database.
if not response.capture:
return _
# Extract data, headers and status from `lib_response`
response.insert(url=url, response=data, headers=dict(headers), status=_.status)
return _
return inner_func
class MockResponse(BaseMockResponse):
# A basic Mock Response for spoofing data, headers and status
def __init__(self, status, data, headers={}):
super().__init__(status, data, headers)
pass
def install():
# A basic install method for Monkey Patching the library
lib_call = library.call
wrapped_lib_call = urlopen_wrapper(lib_call)
response.mpatch.setattr("library.call", wrapped_lib_call)
return
def uninstall():
# A basic uninstall method
response.mpatch.undo()
📁Database
pytest-remote-response
has ResponseDB
to facilitate talking to the database. Internally, its actually a wrapper for sqlite3
.
Primary communication methods are insert()
and get()
.
Note
Some fields such as headers
and data
are compressed using zlib
to reduce the
over-all 🦶footprint.
Fields |
Dumped as |
---|---|
url |
|
cache_date |
|
status |
|
headers |
|
response |
|
Example
# Import the database interface :class:`pytest_response.database.ResponseDB`
from base64 import b64encode
from pytest_response.database import ResponseDB
# Setup the database
db = ResponseDB(path="database.json")
# Insert new element
url = "https://www.python.org"
status = 200
headers = {}
data = b""
db.insert(url=url, status=status, response=data, headers=headers)
# Verify the index
assert b64encode(url.encode()).decode() in db.index()
# Query for an URL
url = "https://www.python.org"
get_status, get_data, get_headers = db.get(url)
# Verify the data is unchanged
assert status == get_status
assert data == get_data
assert headers == get_headers