for all kinds of socket animals, web-clients included - with gevent/asyncio/SSL support
...and then MicroPython's urequests (mocket >= 3.9.1)
In a nutshell, Mocket is monkey-patching on steroids for the socket
and ssl
modules.
It’s designed to serve two main purposes:
To demonstrate that Mocket is more than just a web client mocking tool, it even includes a simple Redis mock.
The main goal of Mocket is to make it easier to test Python clients that communicate using the socket
protocol.
Mocket packages are available for openSUSE, NixOS, ALT Linux, NetBSD, and of course from PyPI.
Starting from 3.7.0, Mocket major version will follow the same numbering pattern as Python's and therefore indicate the most recent Python version that is supported.
FYI: the last version compatible with Python 2.7 is 3.9.4, bugfixing or backporting of features introduced after that release will only be available as commercial support.
Star the project on GitHub, Buy Me a Coffee clicking the button below or, even better, contribute with patches or documentation.
Read the following blog posts if you want to have a big picture of what Mocket is capable of:
The starting point to understand how to use Mocket to write a custom mock is the following example:
As next step, you are invited to have a look at the implementation of both the mocks it provides:
Please also have a look at the huge test suite:
Using pip:
$ pip install mocket
Mocket uses xxhash when available instead of hashlib.md5 for creating hashes, you can install it as follows:
$ pip install mocket[speedups]
When opening an Issue, please add few lines of code as failing test, or -better- open its relative Pull request adding this test to our test suite.
Example of how to mock an HTTP[S] callLet's create a new virtualenv with all we need:
$ python3 -m venv example $ source example/bin/activate $ pip install pytest requests mocket
As second step, we create an example.py file as the following one:
import json from mocket import mocketize from mocket.mocks.mockhttp import Entry import requests import pytest @pytest.fixture def response(): return { "integer": 1, "string": "asd", "boolean": False, } @mocketize # use its decorator def test_json(response): url_to_mock = 'https://testme.org/json' Entry.single_register( Entry.GET, url_to_mock, body=json.dumps(response), headers={'content-type': 'application/json'} ) mocked_response = requests.get(url_to_mock).json() assert response == mocked_response # OR use its context manager from mocket import Mocketizer def test_json_with_context_manager(response): url_to_mock = 'https://testme.org/json' Entry.single_register( Entry.GET, url_to_mock, body=json.dumps(response), headers={'content-type': 'application/json'} ) with Mocketizer(): mocked_response = requests.get(url_to_mock).json() assert response == mocked_response
Let's fire our example test:
$ py.test example.pyHow to make Mocket fail when it tries to write to a real socket?
NEW!!! Sometimes you just want your tests to fail when they attempt to use the network.
with Mocketizer(strict_mode=True): with pytest.raises(StrictMocketException): requests.get("https://duckduckgo.com/") # OR @mocketize(strict_mode=True) def test_get(): with pytest.raises(StrictMocketException): requests.get("https://duckduckgo.com/")
You can specify exceptions as a list of hosts or host-port pairs.
with Mocketizer(strict_mode=True, strict_mode_allowed=["localhost", ("intake.ourmetrics.net", 443)]): ... # OR @mocketize(strict_mode=True, strict_mode_allowed=["localhost", ("intake.ourmetrics.net", 443)]) def test_get(): ...How to be sure that all the Entry instances have been served?
Add this instruction at the end of the test execution:
Mocket.assert_fail_if_entries_not_served()Example of how to fake socket errors
It's very important that we test non-happy paths.
@mocketize def test_raise_exception(self): url = "http://github.com/fluidicon.png" Entry.single_register(Entry.GET, url, exception=socket.error()) with self.assertRaises(requests.exceptions.ConnectionError): requests.get(url)Example of how to record real socket traffic
You probably know what VCRpy is capable of, that's the mocket's way of achieving it:
@mocketize(truesocket_recording_dir=tempfile.mkdtemp()) def test_truesendall_with_recording_https(): url = 'https://httpbin.org/ip' requests.get(url, headers={"Accept": "application/json"}) resp = requests.get(url, headers={"Accept": "application/json"}) assert resp.status_code == 200 dump_filename = os.path.join( Mocket.get_truesocket_recording_dir(), Mocket.get_namespace() + '.json', ) with io.open(dump_filename) as f: response = json.load(f) assert len(response['httpbin.org']['443'].keys()) == 1HTTPretty compatibility layer
Mocket HTTP mock can work as HTTPretty replacement for many different use cases. Two main features are missing:
Two features which are against the Zen of Python, at least imho (mindflayer), but of course I am open to call it into question.
Example:
import json import aiohttp import asyncio from unittest import TestCase from mocket.plugins.httpretty import httpretty, httprettified class AioHttpEntryTestCase(TestCase): @httprettified def test_https_session(self): url = 'https://httpbin.org/ip' httpretty.register_uri( httpretty.GET, url, body=json.dumps(dict(origin='127.0.0.1')), ) async def main(l): async with aiohttp.ClientSession( loop=l, timeout=aiohttp.ClientTimeout(total=3) ) as session: async with session.get(url) as get_response: assert get_response.status == 200 assert await get_response.text() == '{"origin": "127.0.0.1"}' loop = asyncio.new_event_loop() loop.set_debug(True) loop.run_until_complete(main(loop))What about the other socket animals?
Using Mocket with asyncio based clients:
$ pip install aiohttp
Example:
# `aiohttp` creates SSLContext instances at import-time # that's why Mocket would get stuck when dealing with HTTP # Importing the module while Mocket is in control (inside a # decorated test function or using its context manager would # be enough for making it work), the alternative is using a # custom TCPConnector which always return a FakeSSLContext # from Mocket like this example is showing. import aiohttp import pytest from mocket import async_mocketize from mocket.mocks.mockhttp import Entry from mocket.plugins.aiohttp_connector import MocketTCPConnector @pytest.mark.asyncio @async_mocketize async def test_aiohttp(): """ The alternative to using the custom `connector` would be importing `aiohttp` when Mocket is already in control (inside the decorated test). """ url = "https://bar.foo/" data = {"message": "Hello"} Entry.single_register( Entry.GET, url, body=json.dumps(data), headers={"content-type": "application/json"}, ) async with aiohttp.ClientSession( timeout=aiohttp.ClientTimeout(total=3), connector=MocketTCPConnector() ) as session, session.get(url) as response: response = await response.json() assert response == data
Using Mocket as pook engine:
$ pip install mocket[pook]
Example:
import pook from mocket.plugins.pook_mock_engine import MocketEngine pook.set_mock_engine(MocketEngine) pook.on() url = 'http://twitter.com/api/1/foobar' status = 404 response_json = {'error': 'foo'} mock = pook.get( url, headers={'content-type': 'application/json'}, reply=status, response_json=response_json, ) mock.persist() requests.get(url) assert mock.calls == 1 resp = requests.get(url) assert resp.status_code == status assert resp.json() == response_json assert mock.calls == 2
EuroPython 2013, Florence
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4