[{"data":1,"prerenderedAt":2030},["ShallowReactive",2],{"page-\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines\u002F":3},{"id":4,"title":5,"body":6,"description":1989,"extension":1990,"meta":1991,"navigation":374,"path":2026,"seo":2027,"stem":2028,"__hash__":2029},"content\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines\u002Findex.md","Patching Async Code & Coroutines",{"type":7,"value":8,"toc":1968},"minimark",[9,34,39,93,97,120,137,286,290,295,347,354,358,451,461,465,513,528,532,635,656,660,720,726,730,733,771,778,789,793,929,933,939,1072,1092,1100,1104,1107,1159,1210,1235,1359,1363,1366,1469,1489,1501,1516,1520,1526,1561,1567,1584,1597,1601,1604,1619,1632,1642,1652,1713,1717,1720,1726,1776,1785,1794,1804,1807,1810,1828,1832,1849,1864,1887,1906,1922,1926,1957,1964],[10,11,12,13,17,18,21,22,25,26,29,30,33],"p",{},"An async test can pass while proving nothing at all. Replace a coroutine function with a ",[14,15,16],"code",{},"MagicMock",", and calling it returns a ",[14,19,20],{},"Mock"," rather than a coroutine; the code under test awaits it, gets a ",[14,23,24],{},"TypeError"," that a broad ",[14,27,28],{},"except"," swallows, or — more often — never awaits it because the mock stands in for the whole call. The suite is green, ",[14,31,32],{},"RuntimeWarning: coroutine ... was never awaited"," scrolls past unread, and the behaviour is untested. Patching async code correctly is mostly about making that failure mode impossible.",[35,36,38],"h2",{"id":37},"prerequisites","Prerequisites",[40,41,42,54,66,76],"ul",{},[43,44,45,46,49,50,53],"li",{},"Python 3.8+ for ",[14,47,48],{},"unittest.mock.AsyncMock","; 3.10+ for the ",[14,51,52],{},"patch"," detection improvements this guide assumes.",[43,55,56,59,60,65],{},[14,57,58],{},"pytest >= 8.0"," and a runner for the tests themselves — see ",[61,62,64],"a",{"href":63},"\u002Ftesting-async-and-concurrent-python\u002Fpytest-asyncio-in-depth\u002F","pytest-asyncio in depth",".",[43,67,68,69,71,72,65],{},"Working knowledge of ",[14,70,52],{}," target resolution, since nothing changes there for async: ",[61,73,75],{"href":74},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-strategies-for-complex-codebases\u002Fwhere-to-patch-understanding-mock-patch-targets\u002F","where to patch",[43,77,78,79,81,82,84,85,88,89,65],{},"The distinction between ",[14,80,20],{},", ",[14,83,16],{}," and ",[14,86,87],{},"AsyncMock"," covered in ",[61,90,92],{"href":91},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdeep-dive-into-unittestmock\u002Fmock-vs-magicmock-vs-asyncmock-when-to-use-each\u002F","Mock vs MagicMock vs AsyncMock",[35,94,96],{"id":95},"core-concept-awaitability-is-a-property-of-the-replacement","Core concept: awaitability is a property of the replacement",[10,98,99,101,102,104,105,108,109,112,113,81,116,119],{},[14,100,87],{}," differs from ",[14,103,16],{}," in exactly one significant way: calling it returns a coroutine, so ",[14,106,107],{},"await mock(...)"," works and resolves to ",[14,110,111],{},"return_value",". Everything else — ",[14,114,115],{},"side_effect",[14,117,118],{},"assert_called_with",", attribute auto-creation — behaves the same, with an extra family of await-specific assertions layered on.",[10,121,122,125,126,129,130,133,134,136],{},[14,123,124],{},"patch()"," tries to pick the right class for you by inspecting the target with ",[14,127,128],{},"asyncio.iscoroutinefunction",". When that inspection succeeds, everything works. When it fails — and it fails for callables implemented in C, for objects whose ",[14,131,132],{},"__call__"," is async, for functions wrapped in a decorator that is not a coroutine function itself — you silently get a ",[14,135,16],{},", and the test stops testing.",[138,139,142,278],"figure",{"className":140},[141],"diagram",[143,144,151,152,151,156,151,160,151,178,151,186,151,195,151,205,151,210,151,218,151,223,151,230,151,233,151,237,151,241,151,244,151,249,151,254,151,257,151,260,151,263,151,265,151,267,151,270,151,272,151,274],"svg",{"viewBox":145,"role":146,"ariaLabelledBy":147,"xmlns":150},"0 0 840 290","img",[148,149],"am-t","am-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[153,154,155],"title",{"id":148},"What happens when the wrong mock class replaces a coroutine function",[157,158,159],"desc",{"id":149},"Two paths. With AsyncMock, calling the patched function returns a coroutine, awaiting it yields the configured return value, and the await assertions are meaningful. With MagicMock, calling returns a Mock, awaiting it raises a TypeError or the await is skipped entirely, a never-awaited RuntimeWarning is emitted, and the assertion passes without proving anything.",[161,162,163,164,151],"defs",{},"\n    ",[165,166,173],"marker",{"id":167,"viewBox":168,"refX":169,"refY":170,"markerWidth":171,"markerHeight":171,"orient":172},"am-a","0 0 10 10","9","5","7","auto-start-reverse",[174,175],"path",{"d":176,"fill":177},"M0 0 L10 5 L0 10 z","#3d405b",[179,180],"rect",{"x":181,"y":181,"width":182,"height":183,"rx":184,"fill":185},"0","840","290","14","#fffdf8",[187,188,194],"text",{"x":189,"y":190,"textAnchor":191,"fontSize":192,"fontWeight":193,"fill":177},"420","28","middle","16","700","The same patch, two very different tests",[179,196],{"x":197,"y":198,"width":199,"height":200,"rx":201,"fill":202,"stroke":203,"strokeWidth":204},"26","52","360","212","12","#e6f0ea","#81b29a","2",[187,206,87],{"x":207,"y":208,"textAnchor":191,"fontSize":209,"fontWeight":193,"fill":177},"206","78","12.5",[179,211],{"x":212,"y":213,"width":214,"height":215,"rx":216,"fill":185,"stroke":203,"strokeWidth":217},"50","92","312","36","8","1.6",[187,219,222],{"x":207,"y":220,"textAnchor":191,"fontSize":221,"fill":177},"115","11","client.fetch(\"\u002Fx\") → a coroutine",[224,225],"line",{"x1":207,"y1":226,"x2":207,"y2":227,"stroke":177,"strokeWidth":228,"markerEnd":229},"130","148","1.5","url(#am-a)",[179,231],{"x":212,"y":232,"width":214,"height":215,"rx":216,"fill":185,"stroke":203,"strokeWidth":217},"152",[187,234,236],{"x":207,"y":235,"textAnchor":191,"fontSize":221,"fill":177},"175","await → return_value",[224,238],{"x1":207,"y1":239,"x2":207,"y2":240,"stroke":177,"strokeWidth":228,"markerEnd":229},"190","208",[179,242],{"x":212,"y":200,"width":214,"height":243,"rx":216,"fill":185,"stroke":203,"strokeWidth":217},"38",[187,245,248],{"x":207,"y":246,"textAnchor":191,"fontSize":221,"fontWeight":193,"fill":247},"236","#2a5f49","assert_awaited_once_with() is meaningful",[179,250],{"x":251,"y":198,"width":199,"height":200,"rx":201,"fill":252,"stroke":253,"strokeWidth":204},"454","#fbe9e3","#e07a5f",[187,255,16],{"x":256,"y":208,"textAnchor":191,"fontSize":209,"fontWeight":193,"fill":177},"634",[179,258],{"x":259,"y":213,"width":214,"height":215,"rx":216,"fill":185,"stroke":253,"strokeWidth":217},"478",[187,261,262],{"x":256,"y":220,"textAnchor":191,"fontSize":221,"fill":177},"client.fetch(\"\u002Fx\") → a Mock",[224,264],{"x1":256,"y1":226,"x2":256,"y2":227,"stroke":177,"strokeWidth":228,"markerEnd":229},[179,266],{"x":259,"y":232,"width":214,"height":215,"rx":216,"fill":185,"stroke":253,"strokeWidth":217},[187,268,269],{"x":256,"y":235,"textAnchor":191,"fontSize":221,"fill":177},"await → TypeError, or never awaited",[224,271],{"x1":256,"y1":239,"x2":256,"y2":240,"stroke":177,"strokeWidth":228,"markerEnd":229},[179,273],{"x":259,"y":200,"width":214,"height":243,"rx":216,"fill":185,"stroke":253,"strokeWidth":217},[187,275,277],{"x":256,"y":246,"textAnchor":191,"fontSize":221,"fontWeight":193,"fill":276},"#8f3d22","assert_called_once_with() passes regardless",[279,280,281,282,285],"figcaption",{},"The right-hand column is the dangerous one precisely because it is green. ",[14,283,284],{},"assert_called_once_with"," is satisfied by the call alone, whether or not anything awaited the result.",[35,287,289],{"id":288},"step-by-step-implementation","Step-by-step implementation",[291,292,294],"h3",{"id":293},"_1-make-the-warning-fatal","1. Make the warning fatal",[296,297,302],"pre",{"className":298,"code":299,"language":300,"meta":301,"style":301},"language-toml shiki shiki-themes github-light github-dark","# pyproject.toml\n[tool.pytest.ini_options]\nfilterwarnings = [\n    \"error\",\n    # A never-awaited coroutine in a test is a broken test, not a nuisance.\n    \"error::RuntimeWarning\",\n]\n","toml","",[14,303,304,311,317,323,329,335,341],{"__ignoreMap":301},[305,306,308],"span",{"class":224,"line":307},1,[305,309,310],{},"# pyproject.toml\n",[305,312,314],{"class":224,"line":313},2,[305,315,316],{},"[tool.pytest.ini_options]\n",[305,318,320],{"class":224,"line":319},3,[305,321,322],{},"filterwarnings = [\n",[305,324,326],{"class":224,"line":325},4,[305,327,328],{},"    \"error\",\n",[305,330,332],{"class":224,"line":331},5,[305,333,334],{},"    # A never-awaited coroutine in a test is a broken test, not a nuisance.\n",[305,336,338],{"class":224,"line":337},6,[305,339,340],{},"    \"error::RuntimeWarning\",\n",[305,342,344],{"class":224,"line":343},7,[305,345,346],{},"]\n",[10,348,349,350,65],{},"This is the single highest-value line in the file. Every mis-patched async call becomes a failure at the point it happens, rather than a warning nobody reads. The general technique is covered in ",[61,351,353],{"href":352},"\u002Fadvanced-pytest-architecture-configuration\u002Fpytest-configuration-best-practices\u002Fturning-warnings-into-errors-with-filterwarnings\u002F","turning warnings into errors with filterwarnings",[291,355,357],{"id":356},"_2-patch-with-autospec","2. Patch with autospec",[296,359,363],{"className":360,"code":361,"language":362,"meta":301,"style":301},"language-python shiki shiki-themes github-light github-dark","from unittest.mock import patch\n\nimport pytest\n\n\nasync def test_order_service_fetches_the_invoice():\n    # autospec inspects the real attribute: coroutine functions become\n    # AsyncMock, ordinary ones MagicMock, and both get the real signature.\n    with patch(\"myapp.billing.BillingClient.fetch_invoice\", autospec=True) as fetch:\n        fetch.return_value = Invoice(id=\"inv_1\", total_minor=1234)\n\n        result = await OrderService(BillingClient()).total_for(\"inv_1\")\n\n    assert result == 1234\n    # Note: awaited, not merely called.\n    fetch.assert_awaited_once()\n","python",[14,364,365,370,376,381,385,389,394,399,405,411,417,422,428,433,439,445],{"__ignoreMap":301},[305,366,367],{"class":224,"line":307},[305,368,369],{},"from unittest.mock import patch\n",[305,371,372],{"class":224,"line":313},[305,373,375],{"emptyLinePlaceholder":374},true,"\n",[305,377,378],{"class":224,"line":319},[305,379,380],{},"import pytest\n",[305,382,383],{"class":224,"line":325},[305,384,375],{"emptyLinePlaceholder":374},[305,386,387],{"class":224,"line":331},[305,388,375],{"emptyLinePlaceholder":374},[305,390,391],{"class":224,"line":337},[305,392,393],{},"async def test_order_service_fetches_the_invoice():\n",[305,395,396],{"class":224,"line":343},[305,397,398],{},"    # autospec inspects the real attribute: coroutine functions become\n",[305,400,402],{"class":224,"line":401},8,[305,403,404],{},"    # AsyncMock, ordinary ones MagicMock, and both get the real signature.\n",[305,406,408],{"class":224,"line":407},9,[305,409,410],{},"    with patch(\"myapp.billing.BillingClient.fetch_invoice\", autospec=True) as fetch:\n",[305,412,414],{"class":224,"line":413},10,[305,415,416],{},"        fetch.return_value = Invoice(id=\"inv_1\", total_minor=1234)\n",[305,418,420],{"class":224,"line":419},11,[305,421,375],{"emptyLinePlaceholder":374},[305,423,425],{"class":224,"line":424},12,[305,426,427],{},"        result = await OrderService(BillingClient()).total_for(\"inv_1\")\n",[305,429,431],{"class":224,"line":430},13,[305,432,375],{"emptyLinePlaceholder":374},[305,434,436],{"class":224,"line":435},14,[305,437,438],{},"    assert result == 1234\n",[305,440,442],{"class":224,"line":441},15,[305,443,444],{},"    # Note: awaited, not merely called.\n",[305,446,448],{"class":224,"line":447},16,[305,449,450],{},"    fetch.assert_awaited_once()\n",[10,452,453,456,457,65],{},[14,454,455],{},"autospec=True"," makes the signature real, so a call with a renamed keyword fails here instead of in production. The argument for using it everywhere is made in ",[61,458,460],{"href":459},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fautospec-strict-mocking\u002Fcreate-autospec-vs-patch-autospec-true\u002F","create_autospec vs patch(autospec=True)",[291,462,464],{"id":463},"_3-force-the-class-when-detection-fails","3. Force the class when detection fails",[296,466,468],{"className":360,"code":467,"language":362,"meta":301,"style":301},"from unittest.mock import AsyncMock, patch\n\n\nasync def test_client_wrapped_in_a_decorator():\n    # `retry` returns a plain function wrapping a coroutine, so iscoroutinefunction\n    # says False and patch would hand back a MagicMock.\n    with patch(\"myapp.clients.fetch_with_retry\", new_callable=AsyncMock) as fetch:\n        fetch.return_value = {\"status\": \"ok\"}\n        assert await consume() == \"ok\"\n",[14,469,470,475,479,483,488,493,498,503,508],{"__ignoreMap":301},[305,471,472],{"class":224,"line":307},[305,473,474],{},"from unittest.mock import AsyncMock, patch\n",[305,476,477],{"class":224,"line":313},[305,478,375],{"emptyLinePlaceholder":374},[305,480,481],{"class":224,"line":319},[305,482,375],{"emptyLinePlaceholder":374},[305,484,485],{"class":224,"line":325},[305,486,487],{},"async def test_client_wrapped_in_a_decorator():\n",[305,489,490],{"class":224,"line":331},[305,491,492],{},"    # `retry` returns a plain function wrapping a coroutine, so iscoroutinefunction\n",[305,494,495],{"class":224,"line":337},[305,496,497],{},"    # says False and patch would hand back a MagicMock.\n",[305,499,500],{"class":224,"line":343},[305,501,502],{},"    with patch(\"myapp.clients.fetch_with_retry\", new_callable=AsyncMock) as fetch:\n",[305,504,505],{"class":224,"line":401},[305,506,507],{},"        fetch.return_value = {\"status\": \"ok\"}\n",[305,509,510],{"class":224,"line":407},[305,511,512],{},"        assert await consume() == \"ok\"\n",[10,514,515,516,519,520,523,524,527],{},"Any time a patched async call produces a ",[14,517,518],{},"TypeError: object Mock can't be used in 'await' expression",", this is the fix. It is also worth fixing upstream: ",[14,521,522],{},"functools.wraps"," on an ",[14,525,526],{},"async def"," wrapper preserves coroutine-ness, and a decorator that does not is a hazard beyond the tests.",[291,529,531],{"id":530},"_4-handle-the-async-protocols","4. Handle the async protocols",[296,533,535],{"className":360,"code":534,"language":362,"meta":301,"style":301},"from unittest.mock import AsyncMock, MagicMock\n\n\ndef make_async_cm(value):\n    \"\"\"An async context manager double, built by hand.\"\"\"\n    cm = MagicMock()\n    cm.__aenter__ = AsyncMock(return_value=value)   # what `async with` binds\n    cm.__aexit__ = AsyncMock(return_value=False)    # False → exceptions propagate\n    return cm\n\n\nasync def test_connection_is_released_on_error(pool):\n    connection = AsyncMock()\n    pool.acquire = MagicMock(return_value=make_async_cm(connection))\n\n    with pytest.raises(ValueError):\n        await run_failing_query(pool)\n\n    # __aexit__ ran, so the connection went back to the pool.\n    pool.acquire.return_value.__aexit__.assert_awaited_once()\n",[14,536,537,542,546,550,555,560,565,570,575,580,584,588,593,598,603,607,612,618,623,629],{"__ignoreMap":301},[305,538,539],{"class":224,"line":307},[305,540,541],{},"from unittest.mock import AsyncMock, MagicMock\n",[305,543,544],{"class":224,"line":313},[305,545,375],{"emptyLinePlaceholder":374},[305,547,548],{"class":224,"line":319},[305,549,375],{"emptyLinePlaceholder":374},[305,551,552],{"class":224,"line":325},[305,553,554],{},"def make_async_cm(value):\n",[305,556,557],{"class":224,"line":331},[305,558,559],{},"    \"\"\"An async context manager double, built by hand.\"\"\"\n",[305,561,562],{"class":224,"line":337},[305,563,564],{},"    cm = MagicMock()\n",[305,566,567],{"class":224,"line":343},[305,568,569],{},"    cm.__aenter__ = AsyncMock(return_value=value)   # what `async with` binds\n",[305,571,572],{"class":224,"line":401},[305,573,574],{},"    cm.__aexit__ = AsyncMock(return_value=False)    # False → exceptions propagate\n",[305,576,577],{"class":224,"line":407},[305,578,579],{},"    return cm\n",[305,581,582],{"class":224,"line":413},[305,583,375],{"emptyLinePlaceholder":374},[305,585,586],{"class":224,"line":419},[305,587,375],{"emptyLinePlaceholder":374},[305,589,590],{"class":224,"line":424},[305,591,592],{},"async def test_connection_is_released_on_error(pool):\n",[305,594,595],{"class":224,"line":430},[305,596,597],{},"    connection = AsyncMock()\n",[305,599,600],{"class":224,"line":435},[305,601,602],{},"    pool.acquire = MagicMock(return_value=make_async_cm(connection))\n",[305,604,605],{"class":224,"line":441},[305,606,375],{"emptyLinePlaceholder":374},[305,608,609],{"class":224,"line":447},[305,610,611],{},"    with pytest.raises(ValueError):\n",[305,613,615],{"class":224,"line":614},17,[305,616,617],{},"        await run_failing_query(pool)\n",[305,619,621],{"class":224,"line":620},18,[305,622,375],{"emptyLinePlaceholder":374},[305,624,626],{"class":224,"line":625},19,[305,627,628],{},"    # __aexit__ ran, so the connection went back to the pool.\n",[305,630,632],{"class":224,"line":631},20,[305,633,634],{},"    pool.acquire.return_value.__aexit__.assert_awaited_once()\n",[10,636,637,640,641,644,645,647,648,650,651,655],{},[14,638,639],{},"__aexit__"," returning ",[14,642,643],{},"False"," rather than a ",[14,646,20],{}," matters: a truthy return value from ",[14,649,639],{}," ",[652,653,654],"em",{},"suppresses"," the exception, so a carelessly configured mock silently swallows the error the test was written to observe.",[291,657,659],{"id":658},"_5-assert-on-awaits-and-on-order","5. Assert on awaits, and on order",[296,661,663],{"className":360,"code":662,"language":362,"meta":301,"style":301},"from unittest.mock import AsyncMock, MagicMock\n\n\nasync def test_writes_happen_after_the_read():\n    parent = MagicMock()\n    parent.attach_mock(AsyncMock(), \"read\")\n    parent.attach_mock(AsyncMock(), \"write\")\n\n    await pipeline(parent.read, parent.write)\n\n    # mock_calls on the parent interleaves both children in real order.\n    assert [name for name, _, _ in parent.mock_calls] == [\"read\", \"write\"]\n",[14,664,665,669,673,677,682,687,692,697,701,706,710,715],{"__ignoreMap":301},[305,666,667],{"class":224,"line":307},[305,668,541],{},[305,670,671],{"class":224,"line":313},[305,672,375],{"emptyLinePlaceholder":374},[305,674,675],{"class":224,"line":319},[305,676,375],{"emptyLinePlaceholder":374},[305,678,679],{"class":224,"line":325},[305,680,681],{},"async def test_writes_happen_after_the_read():\n",[305,683,684],{"class":224,"line":331},[305,685,686],{},"    parent = MagicMock()\n",[305,688,689],{"class":224,"line":337},[305,690,691],{},"    parent.attach_mock(AsyncMock(), \"read\")\n",[305,693,694],{"class":224,"line":343},[305,695,696],{},"    parent.attach_mock(AsyncMock(), \"write\")\n",[305,698,699],{"class":224,"line":401},[305,700,375],{"emptyLinePlaceholder":374},[305,702,703],{"class":224,"line":407},[305,704,705],{},"    await pipeline(parent.read, parent.write)\n",[305,707,708],{"class":224,"line":413},[305,709,375],{"emptyLinePlaceholder":374},[305,711,712],{"class":224,"line":419},[305,713,714],{},"    # mock_calls on the parent interleaves both children in real order.\n",[305,716,717],{"class":224,"line":424},[305,718,719],{},"    assert [name for name, _, _ in parent.mock_calls] == [\"read\", \"write\"]\n",[10,721,722,725],{},[14,723,724],{},"await_args_list"," gives per-mock ordering; only a shared parent gives the global sequence. When a bug is \"the write happened before the read committed\", this is the assertion that catches it.",[35,727,729],{"id":728},"verification","Verification",[10,731,732],{},"Confirm the configuration actually catches a mis-patch by writing one deliberately:",[296,734,736],{"className":360,"code":735,"language":362,"meta":301,"style":301},"from unittest.mock import MagicMock, patch\n\n\nasync def test_deliberately_wrong_patch_now_fails():\n    with patch(\"myapp.clients.fetch\", new=MagicMock(return_value={\"ok\": True})):\n        with pytest.raises(TypeError):\n            await consume()\n",[14,737,738,743,747,751,756,761,766],{"__ignoreMap":301},[305,739,740],{"class":224,"line":307},[305,741,742],{},"from unittest.mock import MagicMock, patch\n",[305,744,745],{"class":224,"line":313},[305,746,375],{"emptyLinePlaceholder":374},[305,748,749],{"class":224,"line":319},[305,750,375],{"emptyLinePlaceholder":374},[305,752,753],{"class":224,"line":325},[305,754,755],{},"async def test_deliberately_wrong_patch_now_fails():\n",[305,757,758],{"class":224,"line":331},[305,759,760],{},"    with patch(\"myapp.clients.fetch\", new=MagicMock(return_value={\"ok\": True})):\n",[305,762,763],{"class":224,"line":337},[305,764,765],{},"        with pytest.raises(TypeError):\n",[305,767,768],{"class":224,"line":343},[305,769,770],{},"            await consume()\n",[296,772,776],{"className":773,"code":775,"language":187,"meta":301},[774],"language-text","E   TypeError: object dict can't be used in 'await' expression\n",[14,777,775],{"__ignoreMap":301},[10,779,780,781,784,785,788],{},"With ",[14,782,783],{},"filterwarnings = [\"error\"]"," in place, the softer variant — where the coroutine is created and dropped — also fails, with ",[14,786,787],{},"RuntimeWarning: coroutine 'fetch' was never awaited"," raised as an error. Seeing both once is what justifies trusting the rest of the suite.",[35,790,792],{"id":791},"troubleshooting","Troubleshooting",[794,795,796,812],"table",{},[797,798,799],"thead",{},[800,801,802,806,809],"tr",{},[803,804,805],"th",{},"Symptom",[803,807,808],{},"Root cause",[803,810,811],{},"Fix",[813,814,815,836,851,868,889,915],"tbody",{},[800,816,817,823,828],{},[818,819,820],"td",{},[14,821,822],{},"object MagicMock can't be used in 'await' expression",[818,824,825,827],{},[14,826,52],{}," detection failed",[818,829,830,833,834],{},[14,831,832],{},"new_callable=AsyncMock",", or ",[14,835,455],{},[800,837,838,843,846],{},[818,839,840],{},[14,841,842],{},"RuntimeWarning: coroutine was never awaited",[818,844,845],{},"A mock replaced the call, so nothing awaited",[818,847,848,849],{},"Make it an error; patch with ",[14,850,87],{},[800,852,853,859,862],{},[818,854,855,858],{},[14,856,857],{},"assert_called_once"," passes but nothing ran",[818,860,861],{},"Assertion is about the call, not the await",[818,863,864,865],{},"Use ",[14,866,867],{},"assert_awaited_once_with",[800,869,870,876,883],{},[818,871,872,873],{},"An exception vanished inside ",[14,874,875],{},"async with",[818,877,878,880,881],{},[14,879,639],{}," returned a truthy ",[14,882,20],{},[818,884,885,886],{},"Set ",[14,887,888],{},"__aexit__ = AsyncMock(return_value=False)",[800,890,891,899,909],{},[818,892,893,896,897],{},[14,894,895],{},"async for"," over a mock raises ",[14,898,24],{},[818,900,901,904,905,908],{},[14,902,903],{},"__aiter__","\u002F",[14,906,907],{},"__anext__"," not configured",[818,910,864,911,914],{},[14,912,913],{},"autospec"," on an async iterable, or set both",[800,916,917,920,923],{},[818,918,919],{},"Await order assertions are unreliable",[818,921,922],{},"Per-mock lists compared across mocks",[818,924,925,926],{},"Attach both to one parent and read ",[14,927,928],{},"mock_calls",[35,930,932],{"id":931},"async-iterators-and-streaming-responses","Async iterators and streaming responses",[10,934,935,936,938],{},"Streaming APIs are the other place hand-configured mocks go wrong, because ",[14,937,895],{}," needs two dunder methods and a sentinel exception.",[296,940,942],{"className":360,"code":941,"language":362,"meta":301,"style":301},"from unittest.mock import MagicMock\n\n\ndef async_iter(items):\n    \"\"\"A double for anything consumed with `async for`.\"\"\"\n    iterator = iter(items)\n\n    async def anext_():\n        try:\n            return next(iterator)\n        except StopIteration:\n            raise StopAsyncIteration          # the sentinel `async for` expects\n\n    mock = MagicMock()\n    mock.__aiter__.return_value = mock\n    mock.__anext__ = anext_\n    return mock\n\n\nasync def test_processes_every_chunk(handler):\n    response = MagicMock()\n    response.aiter_bytes.return_value = async_iter([b\"one\", b\"two\", b\"three\"])\n\n    await handler.consume(response)\n\n    assert handler.chunks == [b\"one\", b\"two\", b\"three\"]\n",[14,943,944,949,953,957,962,967,972,976,981,986,991,996,1001,1005,1010,1015,1020,1025,1029,1033,1038,1044,1050,1055,1061,1066],{"__ignoreMap":301},[305,945,946],{"class":224,"line":307},[305,947,948],{},"from unittest.mock import MagicMock\n",[305,950,951],{"class":224,"line":313},[305,952,375],{"emptyLinePlaceholder":374},[305,954,955],{"class":224,"line":319},[305,956,375],{"emptyLinePlaceholder":374},[305,958,959],{"class":224,"line":325},[305,960,961],{},"def async_iter(items):\n",[305,963,964],{"class":224,"line":331},[305,965,966],{},"    \"\"\"A double for anything consumed with `async for`.\"\"\"\n",[305,968,969],{"class":224,"line":337},[305,970,971],{},"    iterator = iter(items)\n",[305,973,974],{"class":224,"line":343},[305,975,375],{"emptyLinePlaceholder":374},[305,977,978],{"class":224,"line":401},[305,979,980],{},"    async def anext_():\n",[305,982,983],{"class":224,"line":407},[305,984,985],{},"        try:\n",[305,987,988],{"class":224,"line":413},[305,989,990],{},"            return next(iterator)\n",[305,992,993],{"class":224,"line":419},[305,994,995],{},"        except StopIteration:\n",[305,997,998],{"class":224,"line":424},[305,999,1000],{},"            raise StopAsyncIteration          # the sentinel `async for` expects\n",[305,1002,1003],{"class":224,"line":430},[305,1004,375],{"emptyLinePlaceholder":374},[305,1006,1007],{"class":224,"line":435},[305,1008,1009],{},"    mock = MagicMock()\n",[305,1011,1012],{"class":224,"line":441},[305,1013,1014],{},"    mock.__aiter__.return_value = mock\n",[305,1016,1017],{"class":224,"line":447},[305,1018,1019],{},"    mock.__anext__ = anext_\n",[305,1021,1022],{"class":224,"line":614},[305,1023,1024],{},"    return mock\n",[305,1026,1027],{"class":224,"line":620},[305,1028,375],{"emptyLinePlaceholder":374},[305,1030,1031],{"class":224,"line":625},[305,1032,375],{"emptyLinePlaceholder":374},[305,1034,1035],{"class":224,"line":631},[305,1036,1037],{},"async def test_processes_every_chunk(handler):\n",[305,1039,1041],{"class":224,"line":1040},21,[305,1042,1043],{},"    response = MagicMock()\n",[305,1045,1047],{"class":224,"line":1046},22,[305,1048,1049],{},"    response.aiter_bytes.return_value = async_iter([b\"one\", b\"two\", b\"three\"])\n",[305,1051,1053],{"class":224,"line":1052},23,[305,1054,375],{"emptyLinePlaceholder":374},[305,1056,1058],{"class":224,"line":1057},24,[305,1059,1060],{},"    await handler.consume(response)\n",[305,1062,1064],{"class":224,"line":1063},25,[305,1065,375],{"emptyLinePlaceholder":374},[305,1067,1069],{"class":224,"line":1068},26,[305,1070,1071],{},"    assert handler.chunks == [b\"one\", b\"two\", b\"three\"]\n",[10,1073,1074,1077,1078,1081,1082,1084,1085,1088,1089,1091],{},[14,1075,1076],{},"StopAsyncIteration"," rather than ",[14,1079,1080],{},"StopIteration"," is the detail that catches everyone: raising the synchronous sentinel inside ",[14,1083,907],{}," produces a ",[14,1086,1087],{},"RuntimeError"," about a coroutine raising ",[14,1090,1080],{},", which is a confusing report of a simple mistake.",[10,1093,1094,1095,1099],{},"In practice, most streaming tests are better served by the transport-level fakes described in ",[61,1096,1098],{"href":1097},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fmocking-network-and-http-calls\u002Fmocking-httpx-clients-with-respx\u002F","mocking httpx clients with respx",", which produce real response objects with real async iteration. Hand-rolled async iterators are for the cases where the thing being iterated is your own abstraction rather than a library's.",[35,1101,1103],{"id":1102},"patching-where-the-coroutine-is-defined-versus-used","Patching where the coroutine is defined versus used",[10,1105,1106],{},"Target resolution works identically for async code, but one async-specific wrinkle trips people up: a module that binds a coroutine function at import time and then schedules it as a task.",[296,1108,1110],{"className":360,"code":1109,"language":362,"meta":301,"style":301},"# myapp\u002Fworker.py\nimport asyncio\n\nfrom myapp.clients import fetch_invoice          # bound at import time\n\n\nasync def refresh_all(ids):\n    # The name `fetch_invoice` here is worker's own global, not clients'.\n    tasks = [asyncio.create_task(fetch_invoice(i)) for i in ids]\n    return await asyncio.gather(*tasks)\n",[14,1111,1112,1117,1122,1126,1131,1135,1139,1144,1149,1154],{"__ignoreMap":301},[305,1113,1114],{"class":224,"line":307},[305,1115,1116],{},"# myapp\u002Fworker.py\n",[305,1118,1119],{"class":224,"line":313},[305,1120,1121],{},"import asyncio\n",[305,1123,1124],{"class":224,"line":319},[305,1125,375],{"emptyLinePlaceholder":374},[305,1127,1128],{"class":224,"line":325},[305,1129,1130],{},"from myapp.clients import fetch_invoice          # bound at import time\n",[305,1132,1133],{"class":224,"line":331},[305,1134,375],{"emptyLinePlaceholder":374},[305,1136,1137],{"class":224,"line":337},[305,1138,375],{"emptyLinePlaceholder":374},[305,1140,1141],{"class":224,"line":343},[305,1142,1143],{},"async def refresh_all(ids):\n",[305,1145,1146],{"class":224,"line":401},[305,1147,1148],{},"    # The name `fetch_invoice` here is worker's own global, not clients'.\n",[305,1150,1151],{"class":224,"line":407},[305,1152,1153],{},"    tasks = [asyncio.create_task(fetch_invoice(i)) for i in ids]\n",[305,1155,1156],{"class":224,"line":413},[305,1157,1158],{},"    return await asyncio.gather(*tasks)\n",[296,1160,1162],{"className":360,"code":1161,"language":362,"meta":301,"style":301},"from unittest.mock import AsyncMock, patch\n\n\nasync def test_refresh_all_fetches_each_id():\n    # Patch where it is USED — myapp.worker — not where it is defined.\n    with patch(\"myapp.worker.fetch_invoice\", new_callable=AsyncMock) as fetch:\n        fetch.return_value = Invoice(id=\"x\", total_minor=0)\n        await refresh_all([\"a\", \"b\", \"c\"])\n\n    assert fetch.await_count == 3\n",[14,1163,1164,1168,1172,1176,1181,1186,1191,1196,1201,1205],{"__ignoreMap":301},[305,1165,1166],{"class":224,"line":307},[305,1167,474],{},[305,1169,1170],{"class":224,"line":313},[305,1171,375],{"emptyLinePlaceholder":374},[305,1173,1174],{"class":224,"line":319},[305,1175,375],{"emptyLinePlaceholder":374},[305,1177,1178],{"class":224,"line":325},[305,1179,1180],{},"async def test_refresh_all_fetches_each_id():\n",[305,1182,1183],{"class":224,"line":331},[305,1184,1185],{},"    # Patch where it is USED — myapp.worker — not where it is defined.\n",[305,1187,1188],{"class":224,"line":337},[305,1189,1190],{},"    with patch(\"myapp.worker.fetch_invoice\", new_callable=AsyncMock) as fetch:\n",[305,1192,1193],{"class":224,"line":343},[305,1194,1195],{},"        fetch.return_value = Invoice(id=\"x\", total_minor=0)\n",[305,1197,1198],{"class":224,"line":401},[305,1199,1200],{},"        await refresh_all([\"a\", \"b\", \"c\"])\n",[305,1202,1203],{"class":224,"line":407},[305,1204,375],{"emptyLinePlaceholder":374},[305,1206,1207],{"class":224,"line":413},[305,1208,1209],{},"    assert fetch.await_count == 3\n",[10,1211,1212,1213,1216,1217,1220,1221,1223,1224,1227,1228,1230,1231,1234],{},"Patching ",[14,1214,1215],{},"myapp.clients.fetch_invoice"," here would have no effect, because ",[14,1218,1219],{},"worker"," already holds its own reference — the ordinary rule from ",[61,1222,75],{"href":74},". What makes the async version harder to spot is ",[14,1225,1226],{},"create_task",": with a ",[14,1229,16],{}," the task creation itself raises, but inside a ",[14,1232,1233],{},"gather"," the error arrives as one entry among several and is easy to misread as a problem with the concurrency rather than with the patch.",[138,1236,1238,1356],{"className":1237},[141],[143,1239,151,1244,151,1247,151,1250,151,1257,151,1261,151,1265,151,1272,151,1277,151,1281,151,1288,151,1293,151,1296,151,1300,151,1303,151,1307,151,1312,151,1316,151,1318,151,1321,151,1324,151,1330,151,1334,151,1339,151,1343,151,1346,151,1350,151,1353],{"viewBox":1240,"role":146,"ariaLabelledBy":1241,"xmlns":150},"0 0 800 246",[1242,1243],"tgt-t","tgt-d",[153,1245,1246],{"id":1242},"Patching the binding the caller actually uses",[157,1248,1249],{"id":1243},"The clients module defines fetch_invoice. The worker module imported it at module load, creating its own reference. Patching the clients module replaces only the original binding, leaving the worker's reference untouched, while patching the worker module replaces the name the code actually calls.",[161,1251,163,1252,151],{},[165,1253,1255],{"id":1254,"viewBox":168,"refX":169,"refY":170,"markerWidth":171,"markerHeight":171,"orient":172},"tgt-a",[174,1256],{"d":176,"fill":177},[179,1258],{"x":181,"y":181,"width":1259,"height":1260,"rx":184,"fill":185},"800","246",[187,1262,1264],{"x":1263,"y":190,"textAnchor":191,"fontSize":192,"fontWeight":193,"fill":177},"400","Two names, one function object",[179,1266],{"x":1267,"y":1268,"width":1269,"height":1270,"rx":221,"fill":1271,"stroke":177,"strokeWidth":217},"34","56","230","66","#f4f1de",[187,1273,1276],{"x":1274,"y":1275,"textAnchor":191,"fontSize":201,"fontWeight":193,"fill":177},"149","82","myapp.clients",[187,1278,1280],{"x":1274,"y":1279,"textAnchor":191,"fontSize":221,"fill":177},"103","async def fetch_invoice",[224,1282],{"x1":1283,"y1":1284,"x2":1285,"y2":1284,"stroke":1286,"strokeWidth":228,"markerEnd":1287},"268","89","316","rgba(61,64,91,0.4)","url(#tgt-a)",[187,1289,1292],{"x":1290,"y":208,"textAnchor":191,"fontSize":1291,"fill":177},"292","10.5","import",[179,1294],{"x":1295,"y":1268,"width":1269,"height":1270,"rx":221,"fill":1271,"stroke":177,"strokeWidth":217},"322",[187,1297,1299],{"x":1298,"y":1275,"textAnchor":191,"fontSize":201,"fontWeight":193,"fill":177},"437","myapp.worker",[187,1301,1302],{"x":1298,"y":1279,"textAnchor":191,"fontSize":221,"fill":177},"fetch_invoice (its own name)",[179,1304],{"x":1267,"y":1305,"width":1269,"height":1306,"rx":221,"fill":252,"stroke":253,"strokeWidth":204},"150","68",[187,1308,1311],{"x":1274,"y":1309,"textAnchor":191,"fontSize":1310,"fontWeight":193,"fill":177},"176","11.5","patch(\"myapp.clients…\")",[187,1313,1315],{"x":1274,"y":1314,"textAnchor":191,"fontSize":221,"fill":276},"198","worker still calls the real one",[179,1317],{"x":1295,"y":1305,"width":1269,"height":1306,"rx":221,"fill":202,"stroke":203,"strokeWidth":204},[187,1319,1320],{"x":1298,"y":1309,"textAnchor":191,"fontSize":1310,"fontWeight":193,"fill":177},"patch(\"myapp.worker…\")",[187,1322,1323],{"x":1298,"y":1314,"textAnchor":191,"fontSize":221,"fill":247},"the call site is replaced",[179,1325],{"x":1326,"y":1268,"width":1327,"height":1328,"rx":221,"fill":185,"stroke":1329,"strokeWidth":217},"580","188","162","rgba(61,64,91,0.35)",[187,1331,1333],{"x":1332,"y":1275,"textAnchor":191,"fontSize":201,"fontWeight":193,"fill":177},"674","async wrinkle",[187,1335,1338],{"x":1336,"y":1337,"fontSize":221,"fill":177},"596","108","create_task on a",[187,1340,1342],{"x":1336,"y":1341,"fontSize":221,"fill":177},"128","MagicMock raises",[187,1344,1345],{"x":1336,"y":227,"fontSize":221,"fill":177},"inside gather, so the",[187,1347,1349],{"x":1336,"y":1348,"fontSize":221,"fill":177},"168","error looks like a",[187,1351,1352],{"x":1336,"y":1327,"fontSize":221,"fill":276},"concurrency problem",[187,1354,1355],{"x":1336,"y":240,"fontSize":221,"fill":177},"rather than a patch one",[279,1357,1358],{},"The rule is unchanged from synchronous code; only the way the mistake presents itself is different.",[35,1360,1362],{"id":1361},"async-fixtures-that-provide-the-double","Async fixtures that provide the double",[10,1364,1365],{},"Most of the friction in async patching disappears when the double is supplied by a fixture rather than constructed inside each test. The fixture owns the configuration, the tests own the expectations.",[296,1367,1369],{"className":360,"code":1368,"language":362,"meta":301,"style":301},"from unittest.mock import AsyncMock, create_autospec\n\nimport pytest\n\nfrom myapp.billing import BillingClient\n\n\n@pytest.fixture\ndef billing_client():\n    \"\"\"A strict double: real signatures, coroutine methods awaitable.\"\"\"\n    client = create_autospec(BillingClient, instance=True, spec_set=True)\n    # Sensible defaults so most tests configure nothing at all.\n    client.fetch_invoice.return_value = Invoice(id=\"inv_1\", total_minor=1000)\n    client.list_invoices.return_value = []\n    return client\n\n\nasync def test_total_uses_the_fetched_invoice(billing_client):\n    service = OrderService(billing=billing_client)\n    assert await service.total_for(\"inv_1\") == 1000\n    billing_client.fetch_invoice.assert_awaited_once_with(\"inv_1\")\n",[14,1370,1371,1376,1380,1384,1388,1393,1397,1401,1406,1411,1416,1421,1426,1431,1436,1441,1445,1449,1454,1459,1464],{"__ignoreMap":301},[305,1372,1373],{"class":224,"line":307},[305,1374,1375],{},"from unittest.mock import AsyncMock, create_autospec\n",[305,1377,1378],{"class":224,"line":313},[305,1379,375],{"emptyLinePlaceholder":374},[305,1381,1382],{"class":224,"line":319},[305,1383,380],{},[305,1385,1386],{"class":224,"line":325},[305,1387,375],{"emptyLinePlaceholder":374},[305,1389,1390],{"class":224,"line":331},[305,1391,1392],{},"from myapp.billing import BillingClient\n",[305,1394,1395],{"class":224,"line":337},[305,1396,375],{"emptyLinePlaceholder":374},[305,1398,1399],{"class":224,"line":343},[305,1400,375],{"emptyLinePlaceholder":374},[305,1402,1403],{"class":224,"line":401},[305,1404,1405],{},"@pytest.fixture\n",[305,1407,1408],{"class":224,"line":407},[305,1409,1410],{},"def billing_client():\n",[305,1412,1413],{"class":224,"line":413},[305,1414,1415],{},"    \"\"\"A strict double: real signatures, coroutine methods awaitable.\"\"\"\n",[305,1417,1418],{"class":224,"line":419},[305,1419,1420],{},"    client = create_autospec(BillingClient, instance=True, spec_set=True)\n",[305,1422,1423],{"class":224,"line":424},[305,1424,1425],{},"    # Sensible defaults so most tests configure nothing at all.\n",[305,1427,1428],{"class":224,"line":430},[305,1429,1430],{},"    client.fetch_invoice.return_value = Invoice(id=\"inv_1\", total_minor=1000)\n",[305,1432,1433],{"class":224,"line":435},[305,1434,1435],{},"    client.list_invoices.return_value = []\n",[305,1437,1438],{"class":224,"line":441},[305,1439,1440],{},"    return client\n",[305,1442,1443],{"class":224,"line":447},[305,1444,375],{"emptyLinePlaceholder":374},[305,1446,1447],{"class":224,"line":614},[305,1448,375],{"emptyLinePlaceholder":374},[305,1450,1451],{"class":224,"line":620},[305,1452,1453],{},"async def test_total_uses_the_fetched_invoice(billing_client):\n",[305,1455,1456],{"class":224,"line":625},[305,1457,1458],{},"    service = OrderService(billing=billing_client)\n",[305,1460,1461],{"class":224,"line":631},[305,1462,1463],{},"    assert await service.total_for(\"inv_1\") == 1000\n",[305,1465,1466],{"class":224,"line":1040},[305,1467,1468],{},"    billing_client.fetch_invoice.assert_awaited_once_with(\"inv_1\")\n",[10,1470,1471,1474,1475,1477,1478,1481,1482,1485,1486,1488],{},[14,1472,1473],{},"create_autospec(..., instance=True, spec_set=True)"," gives three guarantees at once: coroutine methods become ",[14,1476,87],{},", signatures are enforced, and assigning an attribute the real class does not have raises immediately. That last one catches the commonest drift — a test configuring ",[14,1479,1480],{},"client.get_invoice"," after the method was renamed to ",[14,1483,1484],{},"fetch_invoice",", which on a bare ",[14,1487,16],{}," silently creates a new attribute and passes.",[10,1490,1491,1492,1495,1496,1500],{},"Defaults in the fixture are what keep individual tests short. A test that cares only about the total should not have to configure ",[14,1493,1494],{},"list_invoices",", and a fixture that supplies plausible defaults means the only lines in a test are the ones that matter to it — the same argument made for ",[61,1497,1499],{"href":1498},"\u002Fintegration-database-and-service-testing\u002Ftest-data-factories-and-builders\u002F","test data factories",", applied to collaborators instead of rows.",[10,1502,1503,1504,1507,1508,1511,1512,1515],{},"One caution: a fixture-provided mock is shared for the duration of one test only, but a ",[652,1505,1506],{},"session","-scoped one accumulates calls across tests and will eventually make ",[14,1509,1510],{},"assert_awaited_once"," fail for reasons unrelated to the test that fails. Keep mock fixtures function-scoped, or call ",[14,1513,1514],{},"reset_mock()"," in an autouse fixture if a wider scope is unavoidable.",[35,1517,1519],{"id":1518},"reading-an-async-mocks-recorded-state","Reading an async mock's recorded state",[10,1521,1522,1523,1525],{},"When an await assertion fails, ",[14,1524,87],{}," carries more information than the error message shows, and printing it is usually faster than re-running with a debugger.",[296,1527,1529],{"className":360,"code":1528,"language":362,"meta":301,"style":301},"def describe(mock) -> str:\n    return (\n        f\"called {mock.call_count}×, awaited {mock.await_count}×\\n\"\n        f\"  calls:  {mock.call_args_list}\\n\"\n        f\"  awaits: {mock.await_args_list}\\n\"\n    )\n",[14,1530,1531,1536,1541,1546,1551,1556],{"__ignoreMap":301},[305,1532,1533],{"class":224,"line":307},[305,1534,1535],{},"def describe(mock) -> str:\n",[305,1537,1538],{"class":224,"line":313},[305,1539,1540],{},"    return (\n",[305,1542,1543],{"class":224,"line":319},[305,1544,1545],{},"        f\"called {mock.call_count}×, awaited {mock.await_count}×\\n\"\n",[305,1547,1548],{"class":224,"line":325},[305,1549,1550],{},"        f\"  calls:  {mock.call_args_list}\\n\"\n",[305,1552,1553],{"class":224,"line":331},[305,1554,1555],{},"        f\"  awaits: {mock.await_args_list}\\n\"\n",[305,1557,1558],{"class":224,"line":337},[305,1559,1560],{},"    )\n",[296,1562,1565],{"className":1563,"code":1564,"language":187,"meta":301},[774],"called 2×, awaited 1×\n  calls:  [call('inv_1'), call('inv_2')]\n  awaits: [call('inv_1')]\n",[14,1566,1564],{"__ignoreMap":301},[10,1568,1569,1570,84,1573,1576,1577,1579,1580,1583],{},"A gap between ",[14,1571,1572],{},"call_count",[14,1574,1575],{},"await_count"," is the diagnosis by itself: something created a coroutine and dropped it. That is a real bug in the code under test roughly half the time — a ",[14,1578,1226],{}," that was removed, a branch that forgot its ",[14,1581,1582],{},"await"," — and a bug in the test the other half. Either way it is a fact the assertion error alone does not mention.",[10,1585,1586,1587,1589,1590,1592,1593,1596],{},"The mirror-image case is ",[14,1588,1575],{}," higher than expected with identical arguments, which usually means a retry loop is running where the test assumed one attempt. ",[14,1591,724],{}," shows the repetition directly, and ",[14,1594,1595],{},"mock.await_args"," holds only the last one, which is why asserting on it alone hides the retries.",[35,1598,1600],{"id":1599},"where-patching-async-code-is-the-wrong-tool","Where patching async code is the wrong tool",[10,1602,1603],{},"Three situations look like patching problems and are not.",[10,1605,1606,1610,1611,1613,1614,1618],{},[1607,1608,1609],"strong",{},"Testing concurrency."," Patching a coroutine with an ",[14,1612,87],{}," that returns instantly removes every suspension point, so the interleaving the test was meant to explore no longer exists. Tests about ordering, cancellation or races need real awaits — see ",[61,1615,1617],{"href":1616},"\u002Ftesting-async-and-concurrent-python\u002Ftesting-threads-and-race-conditions\u002F","testing threads and race conditions"," and the cooperative equivalents in the async section.",[10,1620,1621,1624,1625,1627,1628,1631],{},[1607,1622,1623],{},"Testing timeouts."," A mock that returns immediately can never time out. Simulating slowness needs a ",[14,1626,115],{}," that actually awaits (",[14,1629,1630],{},"async def slow(*a, **kw): await asyncio.sleep(10)","), and even then the assertion is usually better expressed against a fake server that stalls.",[10,1633,1634,1637,1638,65],{},[1607,1635,1636],{},"Replacing an entire client."," When five methods of a client need configuring, the mock has become an unnamed second implementation. A fake client with real behaviour is shorter and survives refactoring, as argued in ",[61,1639,1641],{"href":1640},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fspies-fakes-and-hand-rolled-test-doubles\u002F","spies, fakes and hand-rolled test doubles",[10,1643,1644,1645,1647,1648,1651],{},"The common thread is that ",[14,1646,87],{}," is excellent at standing in for ",[652,1649,1650],{},"one"," call at a boundary and poor at standing in for a subsystem. Used at the boundary it makes tests fast and precise; used deeper it removes the behaviour the test exists to check.",[138,1653,1655,1710],{"className":1654},[141],[143,1656,151,1661,151,1664,151,1667,151,1671,151,1675,151,1679,151,1684,151,1688,151,1693,151,1697,151,1700,151,1703,151,1707],{"viewBox":1657,"role":146,"ariaLabelledBy":1658,"xmlns":150},"0 0 820 252",[1659,1660],"depth-t","depth-d",[153,1662,1663],{"id":1659},"How far in to place an async double",[157,1665,1666],{"id":1660},"Three depths. At the outermost transport boundary, a fake produces real response objects and the code's own parsing and retry logic still runs. One level in, an AsyncMock on a single client method is precise and cheap. Deeper still, mocking internal coroutines removes the suspension points and the concurrency behaviour the test was meant to examine.",[179,1668],{"x":181,"y":181,"width":1669,"height":1670,"rx":184,"fill":185},"820","252",[187,1672,1674],{"x":1673,"y":190,"textAnchor":191,"fontSize":192,"fontWeight":193,"fill":177},"410","Depth decides what the test can still observe",[179,1676],{"x":197,"y":212,"width":1677,"height":1678,"rx":221,"fill":202,"stroke":203,"strokeWidth":204},"768","58",[187,1680,1683],{"x":1681,"y":1682,"fontSize":209,"fontWeight":193,"fill":177},"46","74","transport fake — respx, aioresponses",[187,1685,1687],{"x":1681,"y":1686,"fontSize":221,"fill":177},"94","your parsing, retries and timeouts all still run · highest fidelity",[179,1689],{"x":197,"y":1690,"width":1677,"height":1678,"rx":221,"fill":1691,"stroke":1692,"strokeWidth":204},"118","#f7f0da","#f2cc8f",[187,1694,1696],{"x":1681,"y":1695,"fontSize":209,"fontWeight":193,"fill":177},"142","AsyncMock on one boundary method",[187,1698,1699],{"x":1681,"y":1328,"fontSize":221,"fill":177},"precise and fast · asserts the call and the await · no library behaviour",[179,1701],{"x":197,"y":1702,"width":1677,"height":1678,"rx":221,"fill":252,"stroke":253,"strokeWidth":204},"186",[187,1704,1706],{"x":1681,"y":1705,"fontSize":209,"fontWeight":193,"fill":177},"210","mocking internal coroutines",[187,1708,1709],{"x":1681,"y":1269,"fontSize":221,"fill":276},"suspension points removed · concurrency, cancellation and ordering untested",[279,1711,1712],{},"Each step inward trades fidelity for control. The bottom row is where async tests most often stop testing anything about being async.",[35,1714,1716],{"id":1715},"making-async-doubles-behave-like-the-real-thing","Making async doubles behave like the real thing",[10,1718,1719],{},"A double that returns instantly is convenient and, for some tests, wrong. Three behaviours are worth reproducing when the code under test is supposed to cope with them.",[10,1721,1722,1725],{},[1607,1723,1724],{},"Latency",", when the code has a timeout or a concurrency limit to exercise:",[296,1727,1729],{"className":360,"code":1728,"language":362,"meta":301,"style":301},"import asyncio\nfrom unittest.mock import AsyncMock\n\n\nasync def slow(*args, **kwargs):\n    await asyncio.sleep(0.2)          # a real suspension point, not a busy wait\n    return {\"status\": \"ok\"}\n\n\nclient.fetch = AsyncMock(side_effect=slow)\n",[14,1730,1731,1735,1740,1744,1748,1753,1758,1763,1767,1771],{"__ignoreMap":301},[305,1732,1733],{"class":224,"line":307},[305,1734,1121],{},[305,1736,1737],{"class":224,"line":313},[305,1738,1739],{},"from unittest.mock import AsyncMock\n",[305,1741,1742],{"class":224,"line":319},[305,1743,375],{"emptyLinePlaceholder":374},[305,1745,1746],{"class":224,"line":325},[305,1747,375],{"emptyLinePlaceholder":374},[305,1749,1750],{"class":224,"line":331},[305,1751,1752],{},"async def slow(*args, **kwargs):\n",[305,1754,1755],{"class":224,"line":337},[305,1756,1757],{},"    await asyncio.sleep(0.2)          # a real suspension point, not a busy wait\n",[305,1759,1760],{"class":224,"line":343},[305,1761,1762],{},"    return {\"status\": \"ok\"}\n",[305,1764,1765],{"class":224,"line":401},[305,1766,375],{"emptyLinePlaceholder":374},[305,1768,1769],{"class":224,"line":407},[305,1770,375],{"emptyLinePlaceholder":374},[305,1772,1773],{"class":224,"line":413},[305,1774,1775],{},"client.fetch = AsyncMock(side_effect=slow)\n",[10,1777,1778,1781,1782,1784],{},[1607,1779,1780],{},"Failure then success",", for retry logic — ",[14,1783,115],{}," accepts a sequence, and exceptions in it are raised rather than returned:",[296,1786,1788],{"className":360,"code":1787,"language":362,"meta":301,"style":301},"client.fetch = AsyncMock(side_effect=[TimeoutError, TimeoutError, {\"status\": \"ok\"}])\n",[14,1789,1790],{"__ignoreMap":301},[305,1791,1792],{"class":224,"line":307},[305,1793,1787],{},[10,1795,1796,1799,1800,1803],{},[1607,1797,1798],{},"Backpressure",", for anything consuming a stream faster than it is produced, which needs a real ",[14,1801,1802],{},"asyncio.Queue"," rather than a mock at all.",[10,1805,1806],{},"The first two are cheap and turn \"the retry code is covered\" into \"the retry code demonstrably retries twice and then succeeds\". The third is the point at which a mock stops being the right tool, because the behaviour under test is the interaction between producer and consumer and a double for either one removes it.",[10,1808,1809],{},"A useful heuristic: if configuring the double takes more lines than the assertion, the test is probably about the collaborator rather than about the code, and either a fake or a real dependency will be shorter and more honest.",[10,1811,1812,1813,1816,1817,1819,1820,1823,1824,1827],{},"There is one more behaviour worth reproducing deliberately, because forgetting it produces a class of bug that only appears under load: cancellation. A real client being cancelled mid-request raises ",[14,1814,1815],{},"asyncio.CancelledError"," at the await, and the calling code is expected to let it propagate after cleaning up. An ",[14,1818,87],{}," never does that unless told to, so the cleanup path goes untested until a timeout fires in production. Setting ",[14,1821,1822],{},"side_effect=asyncio.CancelledError"," for one test, and asserting the connection was returned to the pool afterwards, closes that gap for the cost of three lines. The same trick covers ",[14,1825,1826],{},"ConnectionResetError"," and the transport-specific exceptions a client can raise mid-stream, all of which reach the caller through exactly the same path.",[35,1829,1831],{"id":1830},"frequently-asked-questions","Frequently Asked Questions",[10,1833,1834,1837,1839,1840,1842,1843,1845,1846,1848],{},[1607,1835,1836],{},"Why does patch() give me a MagicMock instead of an AsyncMock?",[14,1838,124],{}," inspects the target and returns an ",[14,1841,87],{}," only when it detects a coroutine function. A plain function that returns a coroutine, a callable class instance, or a C-implemented method may not be detected, so you get a ",[14,1844,16],{}," whose return value is not awaitable. Pass ",[14,1847,832],{}," explicitly in those cases.",[10,1850,1851,1854,1855,1857,1858,1860,1861,1863],{},[1607,1852,1853],{},"What does 'coroutine was never awaited' mean in a test?","\nSomething produced a coroutine object that nothing consumed — usually a ",[14,1856,16],{}," standing in for an async function, so the call returned a ",[14,1859,20],{}," rather than something awaitable, or an ",[14,1862,1582],{}," that was accidentally removed. Treat the warning as an error in the test configuration; it almost always means the assertion under it proved nothing.",[10,1865,1866,1869,1870,1873,1874,1876,1877,1873,1879,640,1881,1883,1884,1886],{},[1607,1867,1868],{},"How do I patch an async context manager?","\nGive the mock's ",[14,1871,1872],{},"__aenter__"," an ",[14,1875,87],{}," returning the object the body should receive, and ",[14,1878,639],{},[14,1880,87],{},[14,1882,643],{},". ",[14,1885,16],{}," supplies these automatically when the spec is an async context manager, which is the argument for using autospec rather than configuring by hand.",[10,1888,1889,1892,1893,1896,1897,1899,1900,1902,1903,1905],{},[1607,1890,1891],{},"Can I assert on the order of awaits across several mocks?","\nYes, by attaching the mocks to a common parent with ",[14,1894,1895],{},"attach_mock"," and asserting on the parent's ",[14,1898,928],{},", which interleaves the children in call order. ",[14,1901,87],{}," records awaits in ",[14,1904,724],{}," per mock, which gives per-mock ordering but not a global sequence.",[10,1907,1908,1911,1912,1915,1916,1918,1919,1921],{},[1607,1909,1910],{},"Does autospec work on async functions?","\nYes, and it is the recommended approach. ",[14,1913,1914],{},"create_autospec"," inspects the target and produces ",[14,1917,87],{}," for coroutine functions and ",[14,1920,16],{}," for the rest, with signatures enforced on both, so a call with the wrong arguments fails in the test rather than in production.",[35,1923,1925],{"id":1924},"related-guides","Related guides",[40,1927,1928,1935,1942,1947,1952],{},[43,1929,1930,1931,65],{},"Work through the context-manager case in ",[61,1932,1934],{"href":1933},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines\u002Fpatching-an-async-context-manager\u002F","patching an async context manager",[43,1936,1937,1938,65],{},"Assert sequencing precisely with ",[61,1939,1941],{"href":1940},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines\u002Fasserting-await-order-with-asyncmock\u002F","asserting await order with AsyncMock",[43,1943,1944,1945,65],{},"Compare the three mock classes in ",[61,1946,92],{"href":91},[43,1948,1949,1950,65],{},"Get the test runner's loop right first with ",[61,1951,64],{"href":63},[43,1953,1954,1955,65],{},"Replace deep mocking with behaviour using ",[61,1956,1641],{"href":1640},[10,1958,1959,1960],{},"← Back to ",[61,1961,1963],{"href":1962},"\u002Fadvanced-mocking-test-doubles-in-python\u002F","Advanced Mocking & Test Doubles in Python",[1965,1966,1967],"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":301,"searchDepth":313,"depth":313,"links":1969},[1970,1971,1972,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988],{"id":37,"depth":313,"text":38},{"id":95,"depth":313,"text":96},{"id":288,"depth":313,"text":289,"children":1973},[1974,1975,1976,1977,1978],{"id":293,"depth":319,"text":294},{"id":356,"depth":319,"text":357},{"id":463,"depth":319,"text":464},{"id":530,"depth":319,"text":531},{"id":658,"depth":319,"text":659},{"id":728,"depth":313,"text":729},{"id":791,"depth":313,"text":792},{"id":931,"depth":313,"text":932},{"id":1102,"depth":313,"text":1103},{"id":1361,"depth":313,"text":1362},{"id":1518,"depth":313,"text":1519},{"id":1599,"depth":313,"text":1600},{"id":1715,"depth":313,"text":1716},{"id":1830,"depth":313,"text":1831},{"id":1924,"depth":313,"text":1925},"Patch coroutines correctly: AsyncMock autospec rules, async context managers and iterators, awaited-call assertions, and the RuntimeWarning that means a test asserted nothing.","md",{"slug":1992,"type":1993,"breadcrumb":1994,"datePublished":1995,"dateModified":1995,"faq":1996,"howto":2007},"patching-async-code-and-coroutines","topic","Patching Async Code","2026-09-18",[1997,1999,2001,2003,2005],{"q":1836,"a":1998},"patch() inspects the target and returns an AsyncMock only when it detects a coroutine function. A plain function that returns a coroutine, a callable class instance, or a C-implemented method may not be detected, so you get a MagicMock whose return value is not awaitable. Pass new_callable=AsyncMock explicitly in those cases.",{"q":1853,"a":2000},"Something produced a coroutine object that nothing consumed — usually a MagicMock standing in for an async function, so the call returned a Mock rather than something awaitable, or an await that was accidentally removed. Treat the warning as an error in the test configuration; it almost always means the assertion under it proved nothing.",{"q":1868,"a":2002},"Give the mock's __aenter__ an AsyncMock returning the object the body should receive, and __aexit__ an AsyncMock returning False. MagicMock supplies these automatically when the spec is an async context manager, which is the argument for using autospec rather than configuring by hand.",{"q":1891,"a":2004},"Yes, by attaching the mocks to a common parent with attach_mock and asserting on the parent's mock_calls, which interleaves the children in call order. AsyncMock records awaits in await_args_list per mock, which gives per-mock ordering but not a global sequence.",{"q":1910,"a":2006},"Yes, and it is the recommended approach. create_autospec inspects the target and produces AsyncMock for coroutine functions and MagicMock for the rest, with signatures enforced on both, so a call with the wrong arguments fails in the test rather than in production.",{"name":2008,"description":2009,"steps":2010},"How to patch async code without silent no-ops","Use autospec so coroutine functions become AsyncMock, configure async protocol methods properly, and turn the never-awaited warning into an error.",[2011,2014,2017,2020,2023],{"name":2012,"text":2013},"Turn the never-awaited warning into an error","Add a filterwarnings entry promoting RuntimeWarning about un-awaited coroutines to an error so a mis-patched async call fails the test.",{"name":2015,"text":2016},"Patch with autospec","Use patch(..., autospec=True) or create_autospec so coroutine functions are replaced by AsyncMock with the real signature.",{"name":2018,"text":2019},"Configure async protocol methods explicitly when hand-rolling","Set __aenter__, __aexit__ and __anext__ as AsyncMock instances when a spec is unavailable.",{"name":2021,"text":2022},"Assert on awaits, not just calls","Use assert_awaited_with and await_count so the assertion proves the coroutine was consumed, not merely created.",{"name":2024,"text":2025},"Check the order across mocks with a parent","Attach related mocks to one parent and assert on its mock_calls when the sequence between collaborators matters.","\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines",{"title":5,"description":1989},"advanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines\u002Findex","H4hZkNx9BgF9rYbkzuV18_W6Jv_gUEGs4VN82T6DVck",1789718765721]