[{"data":1,"prerenderedAt":739},["ShallowReactive",2],{"page-\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-strategies-for-complex-codebases\u002Fwhere-to-patch-understanding-mock-patch-targets\u002F":3},{"id":4,"title":5,"body":6,"description":705,"extension":706,"meta":707,"navigation":241,"path":735,"seo":736,"stem":737,"__hash__":738},"content\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-strategies-for-complex-codebases\u002Fwhere-to-patch-understanding-mock-patch-targets\u002Findex.md","Where to Patch: Understanding mock.patch Targets",{"type":7,"value":8,"toc":697},"minimark",[9,47,52,80,197,201,208,469,473,533,537,635,639,645,651,657,661,688,693],[10,11,12,13,17,18,21,22,25,26,29,30,34,35,38,39,42,43,46],"p",{},"The single most common ",[14,15,16],"code",{},"unittest.mock"," mistake is patching ",[14,19,20],{},"requests.get"," and watching the real HTTP request fire anyway, because the code under test did ",[14,23,24],{},"from requests import get"," and now resolves ",[14,27,28],{},"get"," in ",[31,32,33],"em",{},"its own"," module namespace — not in ",[14,36,37],{},"requests",". The canonical rule is \"patch where it's looked up, not where it's defined,\" and it falls directly out of how Python binds names: a ",[14,40,41],{},"from","-import copies a reference into the importing module at import time, so patching the source module never touches that copy. This guide explains the binding mechanism, shows the two import forms side by side, and gives a procedure for finding the correct ",[14,44,45],{},"patch"," target every time.",[48,49,51],"h2",{"id":50},"prerequisites","Prerequisites",[53,54,55,65,74],"ul",{},[56,57,58,59,61,62,64],"li",{},"Python 3.x with ",[14,60,16],{}," (",[14,63,45],{}," lives in the standard library; examples target 3.11).",[56,66,67,68,73],{},"A clear mental model of module namespaces and import binding. The ",[69,70,72],"a",{"href":71},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-strategies-for-complex-codebases\u002F","Patching Strategies for Complex Codebases"," overview frames the broader problem.",[56,75,76,77,79],{},"Familiarity with ",[14,78,45],{}," as a decorator\u002Fcontext manager.",[81,82,85,193],"figure",{"className":83},[84],"diagram",[86,87,94,95,94,99,94,103,94,94,113,94,124,94,130,94,134,94,94,138,94,142,94,146,94,149,94,94,152,94,158,94,162,94,94,166,94,174,94,179,94,183,94,187],"svg",{"viewBox":88,"role":89,"ariaLabelledBy":90,"xmlns":93},"0 0 760 320","img",[91,92],"wtpTITLE","wtpDESC","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[96,97,98],"title",{"id":91},"How from-import binding decides the patch target",[100,101,102],"desc",{"id":92},"A from-import copies the reference into the app module namespace, so the patch must target the app binding, not the service source module.",[104,105,112],"text",{"x":106,"y":107,"textAnchor":108,"fontSize":109,"fontWeight":110,"fill":111},"380","32","middle","17","700","#3d405b","from svc import fn — where the name lives",[114,115],"rect",{"x":116,"y":117,"width":118,"height":119,"rx":120,"fill":121,"stroke":122,"strokeWidth":123},"40","64","200","96","12","#fffdf8","#81b29a","2",[104,125,129],{"x":126,"y":127,"textAnchor":108,"fontSize":128,"fontWeight":110,"fill":111},"140","92","13","svc.py (source)",[104,131,133],{"x":126,"y":132,"textAnchor":108,"fontSize":120,"fill":111},"118","def fn(): ...",[104,135,137],{"x":126,"y":126,"textAnchor":108,"fontSize":136,"fill":111},"11","defines the object",[114,139],{"x":140,"y":117,"width":118,"height":119,"rx":120,"fill":121,"stroke":141,"strokeWidth":123},"520","#e07a5f",[104,143,145],{"x":144,"y":127,"textAnchor":108,"fontSize":128,"fontWeight":110,"fill":111},"620","app.py (caller)",[104,147,148],{"x":144,"y":132,"textAnchor":108,"fontSize":120,"fill":111},"from svc import fn",[104,150,151],{"x":144,"y":126,"textAnchor":108,"fontSize":136,"fill":111},"app.fn = copied ref",[153,154],"line",{"x1":155,"y1":156,"x2":157,"y2":156,"stroke":111,"strokeWidth":123},"242","112","516",[159,160],"polygon",{"points":161,"fill":111},"516,112 504,106 504,118",[104,163,165],{"x":106,"y":164,"textAnchor":108,"fontSize":136,"fill":111},"104","import time: reference copied",[114,167],{"x":168,"y":169,"width":170,"height":171,"rx":120,"fill":172,"stroke":111,"strokeWidth":173},"430","196","290","84","#f4f1de","1.5",[104,175,178],{"x":176,"y":177,"textAnchor":108,"fontSize":120,"fontWeight":110,"fill":111},"575","222","patch(\"app.fn\")  ✓",[104,180,182],{"x":176,"y":181,"textAnchor":108,"fontSize":136,"fill":111},"244","the binding the code reads",[104,184,186],{"x":176,"y":185,"textAnchor":108,"fontSize":136,"fill":141},"264","patch(\"svc.fn\") misses the copy",[153,188],{"x1":144,"y1":189,"x2":144,"y2":190,"stroke":111,"strokeWidth":173,"strokeDashArray":191},"160","194",[192,192],"4",[194,195,196],"figcaption",{},"A from-import copies svc.fn into app's namespace at import time, so the live binding the code resolves is app.fn — that is the patch target. Patching svc.fn leaves app's copy untouched.",[48,198,200],{"id":199},"solution","Solution",[10,202,203,204,207],{},"Trace the import form in the module that ",[31,205,206],{},"calls"," the dependency, then patch the namespace that module reads from.",[209,210,215],"pre",{"className":211,"code":212,"language":213,"meta":214,"style":214},"language-python shiki shiki-themes github-light github-dark","# svc.py — where the function is DEFINED\ndef fetch() -> str:\n    return \"REAL network result\"\n\n\n# app.py — the module UNDER TEST\nfrom svc import fetch          # \u003C-- copies the reference: app.fetch is now a name\n\ndef run() -> str:\n    return fetch()             # resolves `fetch` in app's OWN namespace\n\n\n# test_app.py\nfrom unittest.mock import patch\nimport app\n\n\ndef test_patch_where_looked_up():\n    # CORRECT: patch the binding app actually resolves at call time.\n    with patch(\"app.fetch\", return_value=\"FAKE\") as m:\n        assert app.run() == \"FAKE\"     # the mock replaced app.fetch\n        m.assert_called_once_with()\n\n\ndef test_patch_source_module_fails():\n    # WRONG: app already copied the reference; svc.fetch is a different binding.\n    with patch(\"svc.fetch\", return_value=\"FAKE\"):\n        # app.fetch still points at the original object -> real code runs.\n        assert app.run() == \"REAL network result\"\n\n\n# --- Contrast: attribute-access import makes the source module the target. ---\n# app_attr.py\nimport svc                     # keeps a live reference to the module object\n\ndef run_attr() -> str:\n    return svc.fetch()         # resolves `fetch` on the svc module AT CALL TIME\n\n\ndef test_attribute_access_patches_source():\n    import app_attr\n    # Now patching the source works, because the lookup happens on svc.\n    with patch(\"svc.fetch\", return_value=\"FAKE\"):\n        assert app_attr.run_attr() == \"FAKE\"\n","python","",[14,216,217,224,230,236,243,248,254,260,265,271,277,282,287,293,299,305,310,315,321,327,333,339,345,350,355,361,367,373,379,385,390,395,401,407,413,418,424,430,435,440,446,452,458,463],{"__ignoreMap":214},[218,219,221],"span",{"class":153,"line":220},1,[218,222,223],{},"# svc.py — where the function is DEFINED\n",[218,225,227],{"class":153,"line":226},2,[218,228,229],{},"def fetch() -> str:\n",[218,231,233],{"class":153,"line":232},3,[218,234,235],{},"    return \"REAL network result\"\n",[218,237,239],{"class":153,"line":238},4,[218,240,242],{"emptyLinePlaceholder":241},true,"\n",[218,244,246],{"class":153,"line":245},5,[218,247,242],{"emptyLinePlaceholder":241},[218,249,251],{"class":153,"line":250},6,[218,252,253],{},"# app.py — the module UNDER TEST\n",[218,255,257],{"class":153,"line":256},7,[218,258,259],{},"from svc import fetch          # \u003C-- copies the reference: app.fetch is now a name\n",[218,261,263],{"class":153,"line":262},8,[218,264,242],{"emptyLinePlaceholder":241},[218,266,268],{"class":153,"line":267},9,[218,269,270],{},"def run() -> str:\n",[218,272,274],{"class":153,"line":273},10,[218,275,276],{},"    return fetch()             # resolves `fetch` in app's OWN namespace\n",[218,278,280],{"class":153,"line":279},11,[218,281,242],{"emptyLinePlaceholder":241},[218,283,285],{"class":153,"line":284},12,[218,286,242],{"emptyLinePlaceholder":241},[218,288,290],{"class":153,"line":289},13,[218,291,292],{},"# test_app.py\n",[218,294,296],{"class":153,"line":295},14,[218,297,298],{},"from unittest.mock import patch\n",[218,300,302],{"class":153,"line":301},15,[218,303,304],{},"import app\n",[218,306,308],{"class":153,"line":307},16,[218,309,242],{"emptyLinePlaceholder":241},[218,311,313],{"class":153,"line":312},17,[218,314,242],{"emptyLinePlaceholder":241},[218,316,318],{"class":153,"line":317},18,[218,319,320],{},"def test_patch_where_looked_up():\n",[218,322,324],{"class":153,"line":323},19,[218,325,326],{},"    # CORRECT: patch the binding app actually resolves at call time.\n",[218,328,330],{"class":153,"line":329},20,[218,331,332],{},"    with patch(\"app.fetch\", return_value=\"FAKE\") as m:\n",[218,334,336],{"class":153,"line":335},21,[218,337,338],{},"        assert app.run() == \"FAKE\"     # the mock replaced app.fetch\n",[218,340,342],{"class":153,"line":341},22,[218,343,344],{},"        m.assert_called_once_with()\n",[218,346,348],{"class":153,"line":347},23,[218,349,242],{"emptyLinePlaceholder":241},[218,351,353],{"class":153,"line":352},24,[218,354,242],{"emptyLinePlaceholder":241},[218,356,358],{"class":153,"line":357},25,[218,359,360],{},"def test_patch_source_module_fails():\n",[218,362,364],{"class":153,"line":363},26,[218,365,366],{},"    # WRONG: app already copied the reference; svc.fetch is a different binding.\n",[218,368,370],{"class":153,"line":369},27,[218,371,372],{},"    with patch(\"svc.fetch\", return_value=\"FAKE\"):\n",[218,374,376],{"class":153,"line":375},28,[218,377,378],{},"        # app.fetch still points at the original object -> real code runs.\n",[218,380,382],{"class":153,"line":381},29,[218,383,384],{},"        assert app.run() == \"REAL network result\"\n",[218,386,388],{"class":153,"line":387},30,[218,389,242],{"emptyLinePlaceholder":241},[218,391,393],{"class":153,"line":392},31,[218,394,242],{"emptyLinePlaceholder":241},[218,396,398],{"class":153,"line":397},32,[218,399,400],{},"# --- Contrast: attribute-access import makes the source module the target. ---\n",[218,402,404],{"class":153,"line":403},33,[218,405,406],{},"# app_attr.py\n",[218,408,410],{"class":153,"line":409},34,[218,411,412],{},"import svc                     # keeps a live reference to the module object\n",[218,414,416],{"class":153,"line":415},35,[218,417,242],{"emptyLinePlaceholder":241},[218,419,421],{"class":153,"line":420},36,[218,422,423],{},"def run_attr() -> str:\n",[218,425,427],{"class":153,"line":426},37,[218,428,429],{},"    return svc.fetch()         # resolves `fetch` on the svc module AT CALL TIME\n",[218,431,433],{"class":153,"line":432},38,[218,434,242],{"emptyLinePlaceholder":241},[218,436,438],{"class":153,"line":437},39,[218,439,242],{"emptyLinePlaceholder":241},[218,441,443],{"class":153,"line":442},40,[218,444,445],{},"def test_attribute_access_patches_source():\n",[218,447,449],{"class":153,"line":448},41,[218,450,451],{},"    import app_attr\n",[218,453,455],{"class":153,"line":454},42,[218,456,457],{},"    # Now patching the source works, because the lookup happens on svc.\n",[218,459,461],{"class":153,"line":460},43,[218,462,372],{},[218,464,466],{"class":153,"line":465},44,[218,467,468],{},"        assert app_attr.run_attr() == \"FAKE\"\n",[48,470,472],{"id":471},"why-this-works","Why this works",[10,474,475,478,479,482,483,486,487,490,491,494,495,498,499,29,502,504,505,508,509,512,513,515,516,518,519,521,522,524,525,528,529,532],{},[14,476,477],{},"from svc import fetch"," executes an assignment: it copies the ",[31,480,481],{},"current"," value of ",[14,484,485],{},"svc.fetch"," into ",[14,488,489],{},"app","'s module dictionary as ",[14,492,493],{},"app.fetch",". From then on, ",[14,496,497],{},"app.run()"," looks up ",[14,500,501],{},"fetch",[14,503,489],{},"'s globals, never consulting ",[14,506,507],{},"svc"," again — so ",[14,510,511],{},"patch(\"svc.fetch\", ...)"," rebinds a name nothing reads. ",[14,514,45],{}," works by setting an attribute on the object named by the dotted path, so you must point it at the exact namespace where the code resolves the name: ",[14,517,493],{}," for a ",[14,520,41],{},"-import, and ",[14,523,485],{}," for ",[14,526,527],{},"import svc"," followed by ",[14,530,531],{},"svc.fetch()",", because that form defers the lookup to call time on the live module object.",[48,534,536],{"id":535},"edge-cases-and-failure-modes","Edge cases and failure modes",[53,538,539,571,585,606,625],{},[56,540,541,545,546,549,550,553,554,556,557,560,561,564,565,567,568,570],{},[542,543,544],"strong",{},"Re-imports and aliases shift the target."," ",[14,547,548],{},"from svc import fetch as grab"," creates ",[14,551,552],{},"app.grab","; patch ",[14,555,552],{},". An ",[14,558,559],{},"import svc as s"," with ",[14,562,563],{},"s.fetch()"," still resolves on the original ",[14,566,507],{}," module object, so patch ",[14,569,485],{},".",[56,572,573,576,577,580,581,584],{},[542,574,575],{},"Class methods are attributes of the class, not the caller."," To replace a method on instances, patch ",[14,578,579],{},"module.ClassName.method"," (or use ",[14,582,583],{},"patch.object(ClassName, \"method\")","); the binding lives on the class regardless of where instances are created.",[56,586,587,545,594,597,598,601,602,570],{},[542,588,589,590,593],{},"Patching builtins and ",[14,591,592],{},"sys.modules"," needs different targeting.",[14,595,596],{},"open",", ",[14,599,600],{},"print",", and module-level singletons resolve through builtins or the import system, not a plain copied name — see ",[69,603,605],{"href":604},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-strategies-for-complex-codebases\u002Fpatching-builtins-and-sys-modules-safely\u002F","patching builtins and sys.modules safely",[56,607,608,614,615,619,620,624],{},[542,609,610,613],{},[14,611,612],{},"autospec=True"," does not change the target, only the double's strictness."," You still patch where the name is looked up; pairing the correct target with ",[69,616,618],{"href":617},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fautospec-strict-mocking\u002F","autospec strict mocking"," catches signature drift once the patch lands. This matters for ",[69,621,623],{"href":622},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fmocking-network-and-http-calls\u002F","mocking network and HTTP calls",", where the wrong target silently lets real requests through.",[56,626,627,630,631,634],{},[542,628,629],{},"Patch ordering with stacked decorators is bottom-up."," Multiple ",[14,632,633],{},"@patch"," decorators inject mocks as arguments in reverse order; a wrong assumption here looks like a wrong target. The mock that does not match its expected call is the misordered one, not necessarily the wrong namespace.",[48,636,638],{"id":637},"frequently-asked-questions","Frequently Asked Questions",[10,640,641,644],{},[542,642,643],{},"Why does patching the function's source module not work?","\nA from-import copies the reference into the importing module's namespace at import time. The code under test resolves the name in its own module, so patching the original source module leaves that copied binding untouched and the real function still runs.",[10,646,647,650],{},[542,648,649],{},"What is the patch where it's looked up rule?","\nPatch the name in the namespace where the code under test reads it, not where the object is defined. If module app imports a function from svc with from svc import fn, patch app.fn, because app.fn is the binding the code resolves.",[10,652,653,656],{},[542,654,655],{},"Does import module then module.func avoid the binding problem?","\nYes. With import svc and a call to svc.fn, the code resolves fn on the svc module object at call time, so patching svc.fn works. The fragile case is from svc import fn, which copies the reference into the caller.",[48,658,660],{"id":659},"related-guides","Related guides",[53,662,663,668,673,681],{},[56,664,665,666,570],{},"For builtins, environment, and module-level singletons, read ",[69,667,605],{"href":604},[56,669,670,671,570],{},"Once the target is right, lock the double's contract with ",[69,672,618],{"href":617},[56,674,675,676,680],{},"If the dependency can be injected instead of patched, ",[69,677,679],{"href":678},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Finjecting-fakes-vs-mocks-in-constructors\u002F","injecting fakes vs mocks in constructors"," avoids target headaches entirely.",[56,682,683,684,570],{},"When the patched object is awaited, choose the class via ",[69,685,687],{"href":686},"\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 — when to use each",[10,689,690,691],{},"← Back to ",[69,692,72],{"href":71},[694,695,696],"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":214,"searchDepth":226,"depth":226,"links":698},[699,700,701,702,703,704],{"id":50,"depth":226,"text":51},{"id":199,"depth":226,"text":200},{"id":471,"depth":226,"text":472},{"id":535,"depth":226,"text":536},{"id":637,"depth":226,"text":638},{"id":659,"depth":226,"text":660},"Apply the patch where it's looked up not where it's defined rule: how from-imports bind names, why patching the source module fails, and how to find the target.","md",{"slug":708,"type":709,"breadcrumb":710,"datePublished":711,"dateModified":711,"faq":712,"howto":719},"where-to-patch-understanding-mock-patch-targets","long_tail","Where to Patch","2026-06-18",[713,715,717],{"q":643,"a":714},"A from-import copies the reference into the importing module's namespace at import time. The code under test resolves the name in its own module, so patching the original source module leaves that copied binding untouched and the real function still runs.",{"q":649,"a":716},"Patch the name in the namespace where the code under test reads it, not where the object is defined. If module app imports a function from svc with from svc import fn, patch app.fn, because app.fn is the binding the code resolves.",{"q":655,"a":718},"Yes. With import svc and a call to svc.fn, the code resolves fn on the svc module object at call time, so patching svc.fn works. The fragile case is from svc import fn, which copies the reference into the caller.",{"name":720,"description":721,"steps":722},"How to choose the correct mock.patch target","Trace how the name is imported in the module under test, then patch the namespace where that module looks the name up.",[723,726,729,732],{"name":724,"text":725},"Find the call site","Locate the module that actually calls the dependency and note the line that invokes it.",{"name":727,"text":728},"Inspect the import form","Check whether that module uses from src import fn (a copied binding) or import src then src.fn (a live attribute lookup).",{"name":730,"text":731},"Patch the lookup namespace","For a from-import, patch module_under_test.fn. For attribute access, patch src.fn. Patch where the name is resolved, not where it is defined.",{"name":733,"text":734},"Verify with assert_called","Assert the patched mock was called; if it was not, the real function ran and the target is wrong.","\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-strategies-for-complex-codebases\u002Fwhere-to-patch-understanding-mock-patch-targets",{"title":5,"description":705},"advanced-mocking-test-doubles-in-python\u002Fpatching-strategies-for-complex-codebases\u002Fwhere-to-patch-understanding-mock-patch-targets\u002Findex","2GdgxjurOvOGOHGcPdiuH4L2LmRWfDniQh0QKraJORc",1781793487827]