[{"data":1,"prerenderedAt":907},["ShallowReactive",2],{"page-\u002Fadvanced-mocking-test-doubles-in-python\u002Fmocking-network-and-http-calls\u002Fblocking-accidental-network-calls-in-pytest\u002F":3},{"id":4,"title":5,"body":6,"description":870,"extension":871,"meta":872,"navigation":82,"path":903,"seo":904,"stem":905,"__hash__":906},"content\u002Fadvanced-mocking-test-doubles-in-python\u002Fmocking-network-and-http-calls\u002Fblocking-accidental-network-calls-in-pytest\u002Findex.md","Blocking Accidental Network Calls in pytest",{"type":7,"value":8,"toc":860},"minimark",[9,13,16,21,44,48,262,284,439,443,465,472,479,483,540,544,547,554,573,580,583,672,676,682,705,708,714,779,783,789,805,819,823,851,856],[10,11,12],"p",{},"A unit test that silently calls a real API is a test that passes on a developer's laptop, times out in a CI container without egress, sends a real email to a real customer from a staging credential, and fails one run in fifty when the third-party service hiccups. None of those failures point at the cause, because nothing in the test says \"this makes a network call\". The call is buried in a client three layers down, reached because someone forgot to fake it.",[10,14,15],{},"Blocking the network in the test process turns every such call into an immediate failure, in the test that made it, with the destination in the message. It is one autouse fixture, a dozen lines, and it is one of the most valuable guards a suite can have — precisely because it catches a mistake that is otherwise invisible until it causes an outage in the build or, worse, a side effect outside it.",[17,18,20],"h2",{"id":19},"prerequisites","Prerequisites",[22,23,24,32,35],"ul",{},[25,26,27,31],"li",{},[28,29,30],"code",{},"pytest >= 8.0",".",[25,33,34],{},"Knowledge of which tests legitimately need a network: integration tests against containers usually use loopback, while tests against a remote sandbox genuinely leave the machine.",[25,36,37,38,43],{},"The autouse guidance from ",[39,40,42],"a",{"href":41},"\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Ftaming-autouse-fixtures-in-large-suites\u002F","taming autouse fixtures in large suites",", since this is the textbook good autouse fixture.",[17,45,47],{"id":46},"solution","Solution",[49,50,55],"pre",{"className":51,"code":52,"language":53,"meta":54,"style":54},"language-python shiki shiki-themes github-light github-dark","# tests\u002Fconftest.py\nimport ipaddress\nimport socket\n\nimport pytest\n\n_ALLOWED_HOSTS = {\"localhost\"}\n\n\ndef _is_loopback(host: str) -> bool:\n    if host in _ALLOWED_HOSTS:\n        return True\n    try:\n        return ipaddress.ip_address(host).is_loopback\n    except ValueError:\n        return False                          # a hostname that is not localhost\n\n\n@pytest.fixture(autouse=True)\ndef block_network(request, monkeypatch):\n    if request.node.get_closest_marker(\"allow_network\"):\n        return                                # explicit, visible opt-out\n\n    real_connect = socket.socket.connect\n\n    def guarded_connect(sock, address):\n        host = address[0] if isinstance(address, tuple) else address\n        if isinstance(host, str) and _is_loopback(host):\n            return real_connect(sock, address)\n        raise RuntimeError(\n            f\"{request.node.nodeid} tried to connect to {address!r}; \"\n            \"fake the client or mark the test @pytest.mark.allow_network\"\n        )\n\n    monkeypatch.setattr(socket.socket, \"connect\", guarded_connect)\n","python","",[28,56,57,65,71,77,84,90,95,101,106,111,117,123,129,135,141,147,153,158,163,169,175,181,187,192,198,203,209,215,221,227,233,239,245,251,256],{"__ignoreMap":54},[58,59,62],"span",{"class":60,"line":61},"line",1,[58,63,64],{},"# tests\u002Fconftest.py\n",[58,66,68],{"class":60,"line":67},2,[58,69,70],{},"import ipaddress\n",[58,72,74],{"class":60,"line":73},3,[58,75,76],{},"import socket\n",[58,78,80],{"class":60,"line":79},4,[58,81,83],{"emptyLinePlaceholder":82},true,"\n",[58,85,87],{"class":60,"line":86},5,[58,88,89],{},"import pytest\n",[58,91,93],{"class":60,"line":92},6,[58,94,83],{"emptyLinePlaceholder":82},[58,96,98],{"class":60,"line":97},7,[58,99,100],{},"_ALLOWED_HOSTS = {\"localhost\"}\n",[58,102,104],{"class":60,"line":103},8,[58,105,83],{"emptyLinePlaceholder":82},[58,107,109],{"class":60,"line":108},9,[58,110,83],{"emptyLinePlaceholder":82},[58,112,114],{"class":60,"line":113},10,[58,115,116],{},"def _is_loopback(host: str) -> bool:\n",[58,118,120],{"class":60,"line":119},11,[58,121,122],{},"    if host in _ALLOWED_HOSTS:\n",[58,124,126],{"class":60,"line":125},12,[58,127,128],{},"        return True\n",[58,130,132],{"class":60,"line":131},13,[58,133,134],{},"    try:\n",[58,136,138],{"class":60,"line":137},14,[58,139,140],{},"        return ipaddress.ip_address(host).is_loopback\n",[58,142,144],{"class":60,"line":143},15,[58,145,146],{},"    except ValueError:\n",[58,148,150],{"class":60,"line":149},16,[58,151,152],{},"        return False                          # a hostname that is not localhost\n",[58,154,156],{"class":60,"line":155},17,[58,157,83],{"emptyLinePlaceholder":82},[58,159,161],{"class":60,"line":160},18,[58,162,83],{"emptyLinePlaceholder":82},[58,164,166],{"class":60,"line":165},19,[58,167,168],{},"@pytest.fixture(autouse=True)\n",[58,170,172],{"class":60,"line":171},20,[58,173,174],{},"def block_network(request, monkeypatch):\n",[58,176,178],{"class":60,"line":177},21,[58,179,180],{},"    if request.node.get_closest_marker(\"allow_network\"):\n",[58,182,184],{"class":60,"line":183},22,[58,185,186],{},"        return                                # explicit, visible opt-out\n",[58,188,190],{"class":60,"line":189},23,[58,191,83],{"emptyLinePlaceholder":82},[58,193,195],{"class":60,"line":194},24,[58,196,197],{},"    real_connect = socket.socket.connect\n",[58,199,201],{"class":60,"line":200},25,[58,202,83],{"emptyLinePlaceholder":82},[58,204,206],{"class":60,"line":205},26,[58,207,208],{},"    def guarded_connect(sock, address):\n",[58,210,212],{"class":60,"line":211},27,[58,213,214],{},"        host = address[0] if isinstance(address, tuple) else address\n",[58,216,218],{"class":60,"line":217},28,[58,219,220],{},"        if isinstance(host, str) and _is_loopback(host):\n",[58,222,224],{"class":60,"line":223},29,[58,225,226],{},"            return real_connect(sock, address)\n",[58,228,230],{"class":60,"line":229},30,[58,231,232],{},"        raise RuntimeError(\n",[58,234,236],{"class":60,"line":235},31,[58,237,238],{},"            f\"{request.node.nodeid} tried to connect to {address!r}; \"\n",[58,240,242],{"class":60,"line":241},32,[58,243,244],{},"            \"fake the client or mark the test @pytest.mark.allow_network\"\n",[58,246,248],{"class":60,"line":247},33,[58,249,250],{},"        )\n",[58,252,254],{"class":60,"line":253},34,[58,255,83],{"emptyLinePlaceholder":82},[58,257,259],{"class":60,"line":258},35,[58,260,261],{},"    monkeypatch.setattr(socket.socket, \"connect\", guarded_connect)\n",[49,263,267],{"className":264,"code":265,"language":266,"meta":54,"style":54},"language-toml shiki shiki-themes github-light github-dark","# pyproject.toml\n[tool.pytest.ini_options]\nmarkers = [\"allow_network: test may open non-loopback network connections\"]\n","toml",[28,268,269,274,279],{"__ignoreMap":54},[58,270,271],{"class":60,"line":61},[58,272,273],{},"# pyproject.toml\n",[58,275,276],{"class":60,"line":67},[58,277,278],{},"[tool.pytest.ini_options]\n",[58,280,281],{"class":60,"line":73},[58,282,283],{},"markers = [\"allow_network: test may open non-loopback network connections\"]\n",[285,286,289,435],"figure",{"className":287},[288],"diagram",[290,291,298,299,298,303,298,307,298,325,298,333,298,342,298,351,298,357,298,361,298,367,298,373,298,378,298,382,298,389,298,393,298,400,298,404,298,413,298,418,298,423,298,427,298,431],"svg",{"viewBox":292,"role":293,"ariaLabelledBy":294,"xmlns":297},"0 0 820 262","img",[295,296],"bn-t","bn-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[300,301,302],"title",{"id":295},"How the network guard decides",[304,305,306],"desc",{"id":296},"Every socket connection from a test passes through the guard. If the test carries the allow_network marker, the connection proceeds. Otherwise a loopback destination proceeds and any other destination raises an error naming the test and the address it tried to reach.",[308,309,310,311,298],"defs",{},"\n    ",[312,313,320],"marker",{"id":314,"viewBox":315,"refX":316,"refY":317,"markerWidth":318,"markerHeight":318,"orient":319},"bn-a","0 0 10 10","9","5","7","auto-start-reverse",[321,322],"path",{"d":323,"fill":324},"M0 0 L10 5 L0 10 z","#3d405b",[326,327],"rect",{"x":328,"y":328,"width":329,"height":330,"rx":331,"fill":332},"0","820","262","14","#fffdf8",[334,335,341],"text",{"x":336,"y":337,"textAnchor":338,"fontSize":339,"fontWeight":340,"fill":324},"410","28","middle","16","700","Two questions per connection",[326,343],{"x":344,"y":345,"width":346,"height":347,"rx":348,"fill":349,"stroke":324,"strokeWidth":350},"26","104","180","60","11","#f4f1de","1.6",[334,352,356],{"x":353,"y":354,"textAnchor":338,"fontSize":355,"fontWeight":340,"fill":324},"116","130","12","socket.connect",[334,358,360],{"x":353,"y":359,"textAnchor":338,"fontSize":348,"fill":324},"150","from any code path",[60,362],{"x1":363,"y1":364,"x2":365,"y2":364,"stroke":324,"strokeWidth":350,"markerEnd":366},"210","134","246","url(#bn-a)",[326,368],{"x":369,"y":345,"width":346,"height":347,"rx":348,"fill":370,"stroke":371,"strokeWidth":372},"252","#f7f0da","#f2cc8f","2",[334,374,377],{"x":375,"y":354,"textAnchor":338,"fontSize":376,"fontWeight":340,"fill":324},"342","11.5","allow_network?",[334,379,381],{"x":375,"y":359,"textAnchor":338,"fontSize":348,"fill":380},"#8a5a00","marker on the test",[60,383],{"x1":384,"y1":385,"x2":386,"y2":387,"stroke":324,"strokeWidth":388,"markerEnd":366},"436","120","512","76","1.5",[60,390],{"x1":384,"y1":391,"x2":386,"y2":392,"stroke":324,"strokeWidth":388,"markerEnd":366},"148","178",[334,394,399],{"x":395,"y":396,"fontSize":397,"fill":398},"470","88","10.5","#2a5f49","yes",[334,401,403],{"x":395,"y":402,"fontSize":397,"fill":324},"176","no",[326,405],{"x":406,"y":407,"width":408,"height":409,"rx":410,"fill":411,"stroke":412,"strokeWidth":372},"518","46","276","54","10","#e6f0ea","#81b29a",[334,414,417],{"x":415,"y":416,"textAnchor":338,"fontSize":376,"fill":324},"656","78","connect normally",[326,419],{"x":406,"y":420,"width":408,"height":421,"rx":410,"fill":332,"stroke":422,"strokeWidth":388},"140","100","rgba(61,64,91,0.35)",[334,424,426],{"x":415,"y":425,"textAnchor":338,"fontSize":376,"fontWeight":340,"fill":324},"164","loopback destination?",[334,428,430],{"x":415,"y":429,"textAnchor":338,"fontSize":348,"fill":398},"188","yes → connect (local server, container)",[334,432,434],{"x":415,"y":363,"textAnchor":338,"fontSize":348,"fill":433},"#8f3d22","no → RuntimeError naming test and host",[436,437,438],"figcaption",{},"Loopback stays open so local servers and port-mapped containers work; everything leaving the machine fails unless the test says otherwise.",[17,440,442],{"id":441},"why-this-works","Why this works",[10,444,445,446,449,450,449,453,456,457,460,461,464],{},"Nearly every networking library in Python — ",[28,447,448],{},"requests",", ",[28,451,452],{},"httpx",[28,454,455],{},"urllib3",", database drivers, SDK clients — eventually calls ",[28,458,459],{},"socket.socket.connect",". Patching that one method therefore intercepts outbound connections from all of them without knowing which libraries the code uses. ",[28,462,463],{},"monkeypatch.setattr"," restores the original at the end of each test, so the guard never leaks between tests or into pytest's own machinery.",[10,466,467,468,471],{},"Inspecting the destination rather than blocking everything keeps legitimate local traffic working. Test servers bound to ",[28,469,470],{},"127.0.0.1",", containers whose ports are mapped to the loopback interface, and local Redis or Postgres instances all connect normally. Only connections that would leave the machine are refused — which is exactly the set that makes a test slow, flaky or dangerous.",[10,473,474,475,478],{},"The error message is part of the design, not an afterthought. It names the test by node id, so the failure is attributed correctly even when it surfaces from a background thread; it names the destination, so the reader immediately knows which service was being called; and it states the two possible fixes. A guard that raised a bare ",[28,476,477],{},"RuntimeError(\"network disabled\")"," would still prevent the call, but it would leave every developer who hits it to reverse-engineer the same three facts from a traceback. Spending a line on the message saves that investigation every single time the guard fires, across the whole life of the suite.",[17,480,482],{"id":481},"edge-cases-and-failure-modes","Edge cases and failure modes",[22,484,485,507,517,523,531],{},[25,486,487,491,492,495,496,499,500,502,503,506],{},[488,489,490],"strong",{},"DNS resolution before connect."," ",[28,493,494],{},"socket.getaddrinfo"," runs before ",[28,497,498],{},"connect"," and can itself hit a remote resolver. Blocking at ",[28,501,498],{}," still stops the connection, but a slow or unreachable resolver can add seconds. Patch ",[28,504,505],{},"getaddrinfo"," too if resolution latency matters.",[25,508,509,512,513,516],{},[488,510,511],{},"C extensions with their own sockets."," A few drivers open sockets in C and bypass the Python method. They are rare; ",[28,514,515],{},"pytest-socket"," handles more of them by disabling socket creation outright.",[25,518,519,522],{},[488,520,521],{},"Docker-in-Docker."," Containers may be reached through a non-loopback bridge address. Add that address, read from the Testcontainers API, to the allowed set rather than disabling the guard.",[25,524,525,491,528,530],{},[488,526,527],{},"Unix domain sockets.",[28,529,498],{}," receives a path string rather than a tuple. The guard above lets strings through as local, which is correct for Unix sockets.",[25,532,533,491,536,539],{},[488,534,535],{},"Async clients.",[28,537,538],{},"asyncio"," transports ultimately use the same socket objects, so the guard applies to them, but the error surfaces inside the event loop. Configure the loop's exception handler, or the test may see a timeout rather than the clear message.",[17,541,543],{"id":542},"finding-the-code-that-reached-out","Finding the code that reached out",[10,545,546],{},"The first time the guard is enabled on an established suite, some tests will fail — and each failure is a genuine discovery. The error names the test and the destination; the traceback shows the call path. Three causes account for nearly all of them.",[10,548,549,550,553],{},"The most common is ",[488,551,552],{},"an unfaked client constructed deep inside the code",", often a telemetry or feature-flag client created at import time that phones home on first use. The fix is to inject it, or to configure it for tests with an offline mode most such libraries provide.",[10,555,556,557,560,561,564,565,568,569,31],{},"The second is ",[488,558,559],{},"a mock at the wrong layer",". The test patched a high-level function, but a sibling code path it did not anticipate calls the real client. Faking at the transport — with ",[28,562,563],{},"respx"," or ",[28,566,567],{},"responses"," — rather than at the function catches every path, as argued in ",[39,570,572],{"href":571},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fmocking-network-and-http-calls\u002F","mocking network and HTTP calls",[10,574,575,576,579],{},"The third is ",[488,577,578],{},"a test that genuinely needs the network"," and was never labelled as such. These get the marker, and usually move to the integration stage where network access is expected.",[10,581,582],{},"On a large suite, a gentler adoption is to run the guard in logging mode first — record the offending test and destination instead of raising — for a week of CI runs, then fix the list and switch to failing. That avoids a single enormous change and gives a complete inventory before anything turns red. Working through the list once cleans up a category of flakiness permanently. From then on, any new accidental call fails on the pull request that introduced it, with a message saying exactly what to do. That is the moment the fix is cheapest: the author has the code open and knows which client they just added. Weeks later, the same call would be a mystery timeout in someone else's build. The guard moves the discovery to the cheapest possible moment.",[285,584,586,669],{"className":585},[288],[290,587,298,592,298,595,298,598,298,602,298,607,298,613,298,616,298,621,298,625,298,629,298,633,298,636,298,638,298,641,298,645,298,648,298,651,298,654,298,656,298,659,298,663,298,666],{"viewBox":588,"role":293,"ariaLabelledBy":589,"xmlns":297},"0 0 800 236",[590,591],"cause-t","cause-d",[300,593,594],{"id":590},"Three causes of accidental network calls",[304,596,597],{"id":591},"Three cards. An unfaked client created at import time, such as telemetry, is fixed by injecting it or using an offline mode. A mock placed at the wrong layer is fixed by faking at the transport instead. A test that genuinely needs the network is fixed by marking it and moving it to the integration stage.",[326,599],{"x":328,"y":328,"width":600,"height":601,"rx":331,"fill":332},"800","236",[334,603,606],{"x":604,"y":337,"textAnchor":338,"fontSize":605,"fontWeight":340,"fill":324},"400","15.5","What the guard finds on its first run",[326,608],{"x":609,"y":610,"width":611,"height":425,"rx":355,"fill":332,"stroke":612,"strokeWidth":372},"24","50","240","#e07a5f",[326,614],{"x":609,"y":610,"width":611,"height":615,"rx":355,"fill":324},"30",[334,617,620],{"x":618,"y":619,"textAnchor":338,"fontSize":355,"fontWeight":340,"fill":332},"144","70","hidden client",[334,622,624],{"x":623,"y":345,"fontSize":348,"fill":324},"40","telemetry, flags, SDKs",[334,626,628],{"x":623,"y":627,"fontSize":348,"fill":324},"126","created at import",[334,630,632],{"x":623,"y":631,"fontSize":348,"fontWeight":340,"fill":433},"170","inject or run offline",[326,634],{"x":635,"y":610,"width":611,"height":425,"rx":355,"fill":332,"stroke":371,"strokeWidth":372},"280",[326,637],{"x":635,"y":610,"width":611,"height":615,"rx":355,"fill":324},[334,639,640],{"x":604,"y":619,"textAnchor":338,"fontSize":355,"fontWeight":340,"fill":332},"wrong-layer mock",[334,642,644],{"x":643,"y":345,"fontSize":348,"fill":324},"296","one path faked,",[334,646,647],{"x":643,"y":627,"fontSize":348,"fill":324},"a sibling path real",[334,649,650],{"x":643,"y":631,"fontSize":348,"fontWeight":340,"fill":380},"fake at the transport",[326,652],{"x":653,"y":610,"width":611,"height":425,"rx":355,"fill":332,"stroke":412,"strokeWidth":372},"536",[326,655],{"x":653,"y":610,"width":611,"height":615,"rx":355,"fill":324},[334,657,658],{"x":415,"y":619,"textAnchor":338,"fontSize":355,"fontWeight":340,"fill":332},"genuine need",[334,660,662],{"x":661,"y":345,"fontSize":348,"fill":324},"552","remote sandbox,",[334,664,665],{"x":661,"y":627,"fontSize":348,"fill":324},"public schema download",[334,667,668],{"x":661,"y":631,"fontSize":348,"fontWeight":340,"fill":398},"mark and move",[436,670,671],{},"Each failure is attributable to one of these three, and each has a specific fix. None of them should be resolved by disabling the guard.",[17,673,675],{"id":674},"hand-rolled-guard-or-pytest-socket","Hand-rolled guard or pytest-socket",[10,677,678,679,681],{},"The fixture above is small enough to own, and owning it means the allowed hosts, the error message and the marker are exactly what the project wants. ",[28,680,515],{}," is the packaged alternative, and it is worth knowing where each is the better choice.",[10,683,684,686,687,690,691,693,694,697,698,701,702,704],{},[28,685,515],{}," disables socket creation entirely with ",[28,688,689],{},"--disable-socket",", which is stricter than blocking ",[28,692,498],{},": code that merely creates a socket without connecting also fails. It offers ",[28,695,696],{},"--allow-hosts"," for a loopback allowlist and an ",[28,699,700],{},"enable_socket"," marker for opting out, and because it works at socket creation it catches a few C-level drivers that a ",[28,703,498],{}," patch misses. Its configuration lives in command-line flags, which some teams prefer and others find easy to lose in CI scripts.",[10,706,707],{},"The hand-rolled guard gives a friendlier error — naming the test, the destination and the fix — and makes the allowlist easy to extend with computed addresses such as a Docker bridge IP read from Testcontainers. It is also trivially adapted to log rather than fail during a migration period, which makes adopting it on a large suite gentler.",[10,709,710,711,713],{},"Either is far better than neither. A reasonable default is the hand-rolled fixture for its clearer failures, switching to ",[28,712,515],{}," if a C-level driver is found bypassing it.",[285,715,717,776],{"className":716},[288],[290,718,298,723,298,726,298,729,298,732,298,735,298,738,298,743,298,747,298,750,298,753,298,757,298,760,298,763,298,767,298,770,298,773],{"viewBox":719,"role":293,"ariaLabelledBy":720,"xmlns":297},"0 0 800 234",[721,722],"ps-t","ps-d",[300,724,725],{"id":721},"Hand-rolled guard compared with pytest-socket",[304,727,728],{"id":722},"Two options. The hand-rolled fixture blocks at connect, gives a custom error naming the test and destination, and is easy to extend with computed allowed addresses or to switch to logging during adoption. pytest-socket blocks at socket creation, catches more C-level drivers, and is configured through command-line flags and an enable marker.",[326,730],{"x":328,"y":328,"width":600,"height":731,"rx":331,"fill":332},"234",[334,733,734],{"x":604,"y":337,"textAnchor":338,"fontSize":605,"fontWeight":340,"fill":324},"Either one beats no guard at all",[326,736],{"x":344,"y":610,"width":737,"height":425,"rx":355,"fill":411,"stroke":412,"strokeWidth":372},"360",[334,739,742],{"x":740,"y":387,"textAnchor":338,"fontSize":741,"fontWeight":340,"fill":324},"206","12.5","hand-rolled fixture",[334,744,746],{"x":745,"y":345,"fontSize":348,"fill":324},"44","blocks socket.connect",[334,748,749],{"x":745,"y":627,"fontSize":348,"fill":324},"custom, actionable message",[334,751,752],{"x":745,"y":391,"fontSize":348,"fill":324},"computed allowlist, log mode",[334,754,756],{"x":745,"y":755,"fontSize":348,"fontWeight":340,"fill":398},"186","the friendlier default",[326,758],{"x":759,"y":610,"width":737,"height":425,"rx":355,"fill":370,"stroke":371,"strokeWidth":372},"414",[334,761,515],{"x":762,"y":387,"textAnchor":338,"fontSize":741,"fontWeight":340,"fill":324},"594",[334,764,766],{"x":765,"y":345,"fontSize":348,"fill":324},"432","blocks socket creation",[334,768,769],{"x":765,"y":627,"fontSize":348,"fill":324},"catches more C drivers",[334,771,772],{"x":765,"y":391,"fontSize":348,"fill":324},"flags + enable_socket marker",[334,774,775],{"x":765,"y":755,"fontSize":348,"fontWeight":340,"fill":380},"the stricter option",[436,777,778],{},"Start with whichever the team will actually keep enabled; a strict guard that gets turned off helps nobody.",[17,780,782],{"id":781},"frequently-asked-questions","Frequently Asked Questions",[10,784,785,788],{},[488,786,787],{},"Why block the network in unit tests at all?","\nBecause an unmocked call makes the test slow, dependent on an external service's availability, and able to cause real side effects — a sent email, a charged card, a written record in a shared environment. Blocking turns every accidental call into an immediate, attributable failure instead of an intermittent one.",[10,790,791,794,795,449,797,800,801,804],{},[488,792,793],{},"Should localhost be allowed?","\nUsually yes. Tests that start a local server, talk to a container on the loopback interface or use a local Redis are legitimate. Allow ",[28,796,470],{},[28,798,799],{},"::1"," and ",[28,802,803],{},"localhost"," explicitly, and block everything else.",[10,806,807,810,811,814,815,818],{},[488,808,809],{},"How do integration tests that need the network opt out?","\nWith a marker the guard checks — ",[28,812,813],{},"@pytest.mark.allow_network"," or similar. The exception is visible on the test itself, can be selected or excluded with ",[28,816,817],{},"-m",", and cannot be applied accidentally.",[17,820,822],{"id":821},"related","Related",[22,824,825,831,838,844],{},[25,826,827,830],{},[39,828,829],{"href":571},"Mocking Network and HTTP Calls"," — faking at the transport so the guard never fires.",[25,832,833,837],{},[39,834,836],{"href":835},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fmocking-network-and-http-calls\u002Fsimulating-timeouts-and-connection-errors\u002F","Simulating Timeouts and Connection Errors"," — testing failure paths without a real network.",[25,839,840,843],{},[39,841,842],{"href":41},"Taming Autouse Fixtures in Large Suites"," — why this fixture passes every audit question.",[25,845,846,850],{},[39,847,849],{"href":848},"\u002Fintegration-database-and-service-testing\u002Fspinning-up-services-with-testcontainers\u002F","Spinning Up Services with Testcontainers"," — local services the guard deliberately allows.",[10,852,853,854],{},"← Back to ",[39,855,829],{"href":571},[857,858,859],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":54,"searchDepth":67,"depth":67,"links":861},[862,863,864,865,866,867,868,869],{"id":19,"depth":67,"text":20},{"id":46,"depth":67,"text":47},{"id":441,"depth":67,"text":442},{"id":481,"depth":67,"text":482},{"id":542,"depth":67,"text":543},{"id":674,"depth":67,"text":675},{"id":781,"depth":67,"text":782},{"id":821,"depth":67,"text":822},"Fail any test that opens a real network connection: an autouse socket guard, allowing localhost and marked tests, pytest-socket, and finding the code that reached out.","md",{"slug":873,"type":874,"breadcrumb":875,"datePublished":876,"dateModified":876,"faq":877,"howto":884},"blocking-accidental-network-calls-in-pytest","article","Blocking Network","2026-09-18",[878,880,882],{"q":787,"a":879},"Because an unmocked call makes the test slow, dependent on an external service's availability, and able to cause real side effects — a sent email, a charged card, a written record in a shared environment. Blocking turns every accidental call into an immediate, attributable failure instead of an intermittent one.",{"q":793,"a":881},"Usually yes. Tests that start a local server, talk to a container on the loopback interface or use a local Redis are legitimate. Allow 127.0.0.1, ::1 and localhost explicitly, and block everything else.",{"q":809,"a":883},"With a marker the guard checks — @pytest.mark.allow_network or similar. The exception is visible on the test itself, can be selected or excluded with -m, and cannot be applied accidentally.",{"name":885,"description":886,"steps":887},"How to block accidental network calls in pytest","Install an autouse guard on socket connections that permits loopback, fails everything else with the test name, and honours an explicit opt-out marker.",[888,891,894,897,900],{"name":889,"text":890},"Register an opt-out marker","Declare allow_network in the pytest markers configuration so strict-markers accepts it.",{"name":892,"text":893},"Patch socket.connect in an autouse fixture","Replace the connect method with a guard that inspects the destination address.",{"name":895,"text":896},"Allow loopback addresses","Pass through connections to 127.0.0.1, ::1 and localhost so local servers and containers still work.",{"name":898,"text":899},"Fail with context","Raise an error naming the test and the destination so the offending code is easy to find.",{"name":901,"text":902},"Honour the marker","Skip the guard when the test carries allow_network, so genuine integration tests remain possible.","\u002Fadvanced-mocking-test-doubles-in-python\u002Fmocking-network-and-http-calls\u002Fblocking-accidental-network-calls-in-pytest",{"title":5,"description":870},"advanced-mocking-test-doubles-in-python\u002Fmocking-network-and-http-calls\u002Fblocking-accidental-network-calls-in-pytest\u002Findex","DOXiiXkil8ESxITD_vG2sTgu-nXlv48DPUl8261tvXg",1789718768864]