Skip to content

Commit ed28805

Browse files
authored
Add Mocket "strict mode" (#171)
* Add Mocket "strict mode".
1 parent 3e101e1 commit ed28805

7 files changed

Lines changed: 109 additions & 37 deletions

File tree

README.rst

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,25 @@ Let's fire our example test::
141141

142142
$ py.test example.py
143143

144+
How to make Mocket fail when it tries to write to a real `socket`?
145+
==================================================================
146+
NEW!!! Sometimes you just want your tests to fail when they attempt to use the network.
144147

145-
How to be sure that all the calls are properly mocked?
146-
======================================================
148+
.. code-block:: python
149+
150+
with Mocketizer(strict_mode=True):
151+
with pytest.raises(StrictMocketException):
152+
requests.get("https://duckduckgo.com/")
153+
154+
# OR
155+
156+
@mocketize(strict_mode=True)
157+
def test_get():
158+
with pytest.raises(StrictMocketException):
159+
requests.get("https://duckduckgo.com/")
160+
161+
How to be sure that all the Entry instances have been served?
162+
=============================================================
147163
Add this instruction at the end of the test execution:
148164

149165
.. code-block:: python

mocket/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33

44
__all__ = ("async_mocketize", "mocketize", "Mocket", "MocketEntry", "Mocketizer")
55

6-
__version__ = "3.10.3"
6+
__version__ = "3.10.4"

mocket/async_mocket.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,10 @@
22
from .utils import get_mocketize
33

44

5-
async def wrapper(test, cls=Mocketizer, truesocket_recording_dir=None, *args, **kwargs):
6-
instance = args[0] if args else None
7-
namespace = None
8-
if truesocket_recording_dir:
9-
namespace = Mocketizer.get_namespace(test, instance)
10-
async with cls(
11-
instance,
12-
namespace=namespace,
13-
truesocket_recording_dir=truesocket_recording_dir,
14-
):
5+
async def wrapper(
6+
test, truesocket_recording_dir=None, strict_mode=False, *args, **kwargs
7+
):
8+
async with Mocketizer.factory(test, truesocket_recording_dir, strict_mode, args):
159
return await test(*args, **kwargs)
1610

1711

mocket/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class MocketException(Exception):
2+
pass
3+
4+
5+
class StrictMocketException(MocketException):
6+
pass

mocket/mocket.py

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,15 @@
1818
from urllib3.util.ssl_ import wrap_socket as urllib3_wrap_socket
1919

2020
from .compat import basestring, byte_type, decode_from_bytes, encode_to_bytes, text_type
21-
from .utils import SSL_PROTOCOL, MocketSocketCore, get_mocketize, hexdump, hexload
21+
from .exceptions import StrictMocketException
22+
from .utils import (
23+
SSL_PROTOCOL,
24+
MocketMode,
25+
MocketSocketCore,
26+
get_mocketize,
27+
hexdump,
28+
hexload,
29+
)
2230

2331
xxh32 = None
2432
try:
@@ -286,6 +294,9 @@ def recv(self, buffersize, flags=None):
286294
raise exc
287295

288296
def true_sendall(self, data, *args, **kwargs):
297+
if MocketMode().STRICT:
298+
raise StrictMocketException("Mocket tried to use the real `socket` module.")
299+
289300
req = decode_from_bytes(data)
290301
# make request unique again
291302
req_signature = _hash_request(hasher, req)
@@ -597,20 +608,17 @@ def get_response(self):
597608

598609

599610
class Mocketizer:
600-
def __init__(self, instance=None, namespace=None, truesocket_recording_dir=None):
611+
def __init__(
612+
self,
613+
instance=None,
614+
namespace=None,
615+
truesocket_recording_dir=None,
616+
strict_mode=False,
617+
):
601618
self.instance = instance
602619
self.truesocket_recording_dir = truesocket_recording_dir
603620
self.namespace = namespace or text_type(id(self))
604-
605-
@staticmethod
606-
def get_namespace(test, instance):
607-
return ".".join(
608-
(
609-
instance.__class__.__module__,
610-
instance.__class__.__name__,
611-
test.__name__,
612-
)
613-
)
621+
MocketMode().STRICT = strict_mode
614622

615623
def enter(self):
616624
Mocket.enable(
@@ -639,22 +647,34 @@ async def __aenter__(self, *args, **kwargs):
639647
async def __aexit__(self, *args, **kwargs):
640648
self.exit()
641649

642-
def check_and_call(self, method):
643-
method = getattr(self.instance, method, None)
650+
def check_and_call(self, method_name):
651+
method = getattr(self.instance, method_name, None)
644652
if callable(method):
645653
method()
646654

655+
@staticmethod
656+
def factory(test, truesocket_recording_dir, strict_mode, args):
657+
instance = args[0] if args else None
658+
namespace = None
659+
if truesocket_recording_dir:
660+
namespace = ".".join(
661+
(
662+
instance.__class__.__module__,
663+
instance.__class__.__name__,
664+
test.__name__,
665+
)
666+
)
647667

648-
def wrapper(test, cls=Mocketizer, truesocket_recording_dir=None, *args, **kwargs):
649-
instance = args[0] if args else None
650-
namespace = None
651-
if truesocket_recording_dir:
652-
namespace = Mocketizer.get_namespace(test, instance)
653-
with cls(
654-
instance,
655-
namespace=namespace,
656-
truesocket_recording_dir=truesocket_recording_dir,
657-
):
668+
return Mocketizer(
669+
instance,
670+
namespace=namespace,
671+
truesocket_recording_dir=truesocket_recording_dir,
672+
strict_mode=strict_mode,
673+
)
674+
675+
676+
def wrapper(test, truesocket_recording_dir=None, strict_mode=False, *args, **kwargs):
677+
with Mocketizer.factory(test, truesocket_recording_dir, strict_mode, args):
658678
return test(*args, **kwargs)
659679

660680

mocket/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,11 @@ def get_mocketize(wrapper_):
4242
if decorator.__version__ < "5": # pragma: no cover
4343
return decorator.decorator(wrapper_)
4444
return decorator.decorator(wrapper_, kwsyntax=True)
45+
46+
47+
class MocketMode:
48+
__shared_state = {}
49+
STRICT = None
50+
51+
def __init__(self):
52+
self.__dict__ = self.__shared_state

tests/main/test_mode.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import pytest
2+
import requests
3+
4+
from mocket import Mocketizer, mocketize
5+
from mocket.exceptions import StrictMocketException
6+
7+
8+
@mocketize(strict_mode=True)
9+
def test_strict_mode_fails():
10+
url = "https://httpbin.org/ip"
11+
12+
with pytest.raises(StrictMocketException):
13+
requests.get(url)
14+
15+
16+
@pytest.mark.skipif('os.getenv("SKIP_TRUE_HTTP", False)')
17+
def test_intermittent_strict_mode():
18+
url = "https://httpbin.org/ip"
19+
20+
with Mocketizer(strict_mode=False):
21+
requests.get(url)
22+
23+
with Mocketizer(strict_mode=True):
24+
with pytest.raises(StrictMocketException):
25+
requests.get(url)
26+
27+
with Mocketizer(strict_mode=False):
28+
requests.get(url)

0 commit comments

Comments
 (0)