[{"data":1,"prerenderedAt":953},["ShallowReactive",2],{"page-\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Ffixture-teardown-with-yield-vs-addfinalizer\u002F":3},{"id":4,"title":5,"body":6,"description":916,"extension":917,"meta":918,"navigation":88,"path":949,"seo":950,"stem":951,"__hash__":952},"content\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Ffixture-teardown-with-yield-vs-addfinalizer\u002Findex.md","Fixture Teardown: yield vs addfinalizer",{"type":7,"value":8,"toc":905},"minimark",[9,25,28,33,52,56,66,227,240,356,360,375,380,384,466,470,479,571,597,697,701,704,712,732,743,815,819,826,829,833,848,854,863,867,896,901],[10,11,12,13,17,18,20,21,24],"p",{},"A fixture that acquires one resource and releases it is the most common thing in any pytest suite, and ",[14,15,16],"code",{},"yield"," handles it perfectly. The trouble starts when setup acquires several things in sequence and the third step fails: with a single ",[14,19,16],{},", the teardown code never runs, and the first two resources leak. That is the situation ",[14,22,23],{},"request.addfinalizer"," exists for, and knowing when to reach for it is what separates fixtures that clean up reliably from fixtures that leak a container or a temporary directory every time setup hiccups.",[10,26,27],{},"Both mechanisms are well supported and neither is deprecated. The choice is about the shape of the setup, not about style, and a suite that uses each where it fits ends up with fixtures that are both readable and robust.",[29,30,32],"h2",{"id":31},"prerequisites","Prerequisites",[34,35,36,43],"ul",{},[37,38,39,42],"li",{},[14,40,41],{},"pytest >= 8.0","; both mechanisms are long-standing and stable.",[37,44,45,46,51],{},"Familiarity with fixture scope and the setup and teardown phases from ",[47,48,50],"a",{"href":49},"\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002F","mastering pytest fixtures",".",[29,53,55],{"id":54},"solution","Solution",[10,57,58,59,61,62,65],{},"Use ",[14,60,16],{}," when there is one resource and one acquisition step. Use ",[14,63,64],{},"addfinalizer"," when setup has several steps, registering each cleanup the moment its resource exists.",[67,68,73],"pre",{"className":69,"code":70,"language":71,"meta":72,"style":72},"language-python shiki shiki-themes github-light github-dark","import pytest\n\n\n@pytest.fixture\ndef temp_bucket(storage):\n    # One resource, one step: yield is clearest.\n    bucket = storage.create_bucket(\"test-bucket\")\n    yield bucket\n    storage.delete_bucket(bucket)          # runs whether the test passed or failed\n\n\n@pytest.fixture\ndef seeded_environment(request, storage, queue, database):\n    # Several resources acquired in sequence. Each cleanup is registered the\n    # moment its resource exists, so a failure in a LATER step still releases\n    # everything acquired so far.\n    bucket = storage.create_bucket(\"seed\")\n    request.addfinalizer(lambda: storage.delete_bucket(bucket))\n\n    subscription = queue.subscribe(\"events\")           # may fail\n    request.addfinalizer(subscription.cancel)\n\n    schema = database.create_schema(\"seed_schema\")     # may also fail\n    request.addfinalizer(lambda: database.drop_schema(schema))\n\n    return {\"bucket\": bucket, \"subscription\": subscription, \"schema\": schema}\n","python","",[14,74,75,83,90,95,101,107,113,119,125,131,136,141,146,152,158,164,170,176,182,187,193,199,204,210,216,221],{"__ignoreMap":72},[76,77,80],"span",{"class":78,"line":79},"line",1,[76,81,82],{},"import pytest\n",[76,84,86],{"class":78,"line":85},2,[76,87,89],{"emptyLinePlaceholder":88},true,"\n",[76,91,93],{"class":78,"line":92},3,[76,94,89],{"emptyLinePlaceholder":88},[76,96,98],{"class":78,"line":97},4,[76,99,100],{},"@pytest.fixture\n",[76,102,104],{"class":78,"line":103},5,[76,105,106],{},"def temp_bucket(storage):\n",[76,108,110],{"class":78,"line":109},6,[76,111,112],{},"    # One resource, one step: yield is clearest.\n",[76,114,116],{"class":78,"line":115},7,[76,117,118],{},"    bucket = storage.create_bucket(\"test-bucket\")\n",[76,120,122],{"class":78,"line":121},8,[76,123,124],{},"    yield bucket\n",[76,126,128],{"class":78,"line":127},9,[76,129,130],{},"    storage.delete_bucket(bucket)          # runs whether the test passed or failed\n",[76,132,134],{"class":78,"line":133},10,[76,135,89],{"emptyLinePlaceholder":88},[76,137,139],{"class":78,"line":138},11,[76,140,89],{"emptyLinePlaceholder":88},[76,142,144],{"class":78,"line":143},12,[76,145,100],{},[76,147,149],{"class":78,"line":148},13,[76,150,151],{},"def seeded_environment(request, storage, queue, database):\n",[76,153,155],{"class":78,"line":154},14,[76,156,157],{},"    # Several resources acquired in sequence. Each cleanup is registered the\n",[76,159,161],{"class":78,"line":160},15,[76,162,163],{},"    # moment its resource exists, so a failure in a LATER step still releases\n",[76,165,167],{"class":78,"line":166},16,[76,168,169],{},"    # everything acquired so far.\n",[76,171,173],{"class":78,"line":172},17,[76,174,175],{},"    bucket = storage.create_bucket(\"seed\")\n",[76,177,179],{"class":78,"line":178},18,[76,180,181],{},"    request.addfinalizer(lambda: storage.delete_bucket(bucket))\n",[76,183,185],{"class":78,"line":184},19,[76,186,89],{"emptyLinePlaceholder":88},[76,188,190],{"class":78,"line":189},20,[76,191,192],{},"    subscription = queue.subscribe(\"events\")           # may fail\n",[76,194,196],{"class":78,"line":195},21,[76,197,198],{},"    request.addfinalizer(subscription.cancel)\n",[76,200,202],{"class":78,"line":201},22,[76,203,89],{"emptyLinePlaceholder":88},[76,205,207],{"class":78,"line":206},23,[76,208,209],{},"    schema = database.create_schema(\"seed_schema\")     # may also fail\n",[76,211,213],{"class":78,"line":212},24,[76,214,215],{},"    request.addfinalizer(lambda: database.drop_schema(schema))\n",[76,217,219],{"class":78,"line":218},25,[76,220,89],{"emptyLinePlaceholder":88},[76,222,224],{"class":78,"line":223},26,[76,225,226],{},"    return {\"bucket\": bucket, \"subscription\": subscription, \"schema\": schema}\n",[10,228,229,230,233,234,236,237,239],{},"If ",[14,231,232],{},"database.create_schema"," raises, pytest reports a setup error — and then runs the two finalizers already registered, releasing the subscription and deleting the bucket. With a single ",[14,235,16],{}," fixture containing all three steps, the same failure would leave both leaked, because the code after ",[14,238,16],{}," is never reached.",[241,242,245,352],"figure",{"className":243},[244],"diagram",[246,247,254,255,254,259,254,263,254,271,254,281,254,291,254,297,254,304,254,308,254,313,254,317,254,321,254,325,254,330,254,334,254,338,254,341,254,343,254,346,254,349],"svg",{"viewBox":248,"role":249,"ariaLabelledBy":250,"xmlns":253},"0 0 820 268","img",[251,252],"yf-t","yf-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[256,257,258],"title",{"id":251},"What gets cleaned up when step three of setup fails",[260,261,262],"desc",{"id":252},"Two fixtures acquire the same three resources in sequence, and the third acquisition fails. The yield fixture never reaches its yield, so its teardown code never runs and the first two resources leak. The addfinalizer fixture registered cleanups after steps one and two, so both run in reverse order and nothing leaks.",[264,265],"rect",{"x":266,"y":266,"width":267,"height":268,"rx":269,"fill":270},"0","820","268","14","#fffdf8",[272,273,280],"text",{"x":274,"y":275,"textAnchor":276,"fontSize":277,"fontWeight":278,"fill":279},"410","28","middle","16","700","#3d405b","Failure in the middle of setup",[264,282],{"x":283,"y":284,"width":285,"height":286,"rx":287,"fill":288,"stroke":289,"strokeWidth":290},"26","52","368","194","12","#fbe9e3","#e07a5f","2",[272,292,296],{"x":293,"y":294,"textAnchor":276,"fontSize":295,"fontWeight":278,"fill":279},"210","78","12.5","one yield fixture",[272,298,303],{"x":299,"y":300,"fontSize":301,"fill":302},"44","106","11","#2a5f49","1 · bucket created",[272,305,307],{"x":299,"y":306,"fontSize":301,"fill":302},"128","2 · subscription opened",[272,309,312],{"x":299,"y":310,"fontSize":301,"fill":311},"150","#8f3d22","3 · create_schema raises",[272,314,316],{"x":299,"y":315,"fontSize":301,"fill":279},"178","yield never reached",[272,318,320],{"x":299,"y":319,"fontSize":301,"fill":279},"200","teardown code never runs",[272,322,324],{"x":299,"y":323,"fontSize":301,"fontWeight":278,"fill":311},"228","bucket and subscription leak",[264,326],{"x":327,"y":284,"width":285,"height":286,"rx":287,"fill":328,"stroke":329,"strokeWidth":290},"426","#e6f0ea","#81b29a",[272,331,333],{"x":332,"y":294,"textAnchor":276,"fontSize":295,"fontWeight":278,"fill":279},"610","addfinalizer per step",[272,335,337],{"x":336,"y":300,"fontSize":301,"fill":302},"444","1 · bucket created → finalizer A",[272,339,340],{"x":336,"y":306,"fontSize":301,"fill":302},"2 · subscription → finalizer B",[272,342,312],{"x":336,"y":310,"fontSize":301,"fill":311},[272,344,345],{"x":336,"y":315,"fontSize":301,"fill":279},"pytest runs B, then A",[272,347,348],{"x":336,"y":319,"fontSize":301,"fill":279},"reverse order of registration",[272,350,351],{"x":336,"y":323,"fontSize":301,"fontWeight":278,"fill":302},"nothing leaks",[353,354,355],"figcaption",{},"The difference only shows when setup fails partway, which is exactly when leaked resources are most costly — a flaky dependency on a CI runner, repeated across every retry.",[29,357,359],{"id":358},"why-this-works","Why this works",[10,361,362,363,365,366,368,369,371,372,374],{},"A ",[14,364,16],{}," fixture is a generator: pytest runs it up to the ",[14,367,16],{},", runs the test, then resumes it to run the rest. If the generator raises before ",[14,370,16],{},", there is nothing to resume, so the post-",[14,373,16],{}," code is simply never executed. That is correct from pytest's point of view — the fixture never finished setting up — and it is exactly why partially-acquired resources leak.",[10,376,377,379],{},[14,378,23],{}," registers a callable on the fixture's request immediately. pytest runs every registered finalizer during teardown, in reverse registration order, regardless of whether the fixture function returned normally or raised. Registering each cleanup right after its resource exists therefore guarantees that whatever was acquired is released, however far setup got.",[29,381,383],{"id":382},"edge-cases-and-failure-modes","Edge cases and failure modes",[34,385,386,410,423,429,444],{},[37,387,388,398,399,402,403,406,407,409],{},[389,390,362,391,393,394,397],"strong",{},[14,392,16],{}," fixture with its own ",[14,395,396],{},"try\u002Ffinally"," around several steps."," This works, but the ",[14,400,401],{},"finally"," must cope with resources that were never acquired — usually by initialising them to ",[14,404,405],{},"None"," and checking. ",[14,408,64],{}," expresses the same thing without the bookkeeping.",[37,411,412,415,416,419,420,51],{},[389,413,414],{},"Finalizers that capture loop variables."," ",[14,417,418],{},"for name in names: request.addfinalizer(lambda: drop(name))"," releases the last name repeatedly. Bind with a default argument or ",[14,421,422],{},"functools.partial",[37,424,425,428],{},[389,426,427],{},"A teardown that raises."," pytest reports a teardown error for the test and continues with the remaining finalizers. Do not swallow the exception; a failing cleanup is a real problem.",[37,430,431,440,441,443],{},[389,432,433,434,436,437,439],{},"Mixing ",[14,435,16],{}," and ",[14,438,64],{}," in one fixture."," It is allowed, and the finalizers run after the post-",[14,442,16],{}," code. It is rarely clearer than choosing one.",[37,445,446,415,449,451,452,455,456,458,459,461,462,465],{},[389,447,448],{},"Async fixtures.",[14,450,64],{}," takes a synchronous callable. For async cleanup in ",[14,453,454],{},"pytest-asyncio"," fixtures, use ",[14,457,16],{}," with ",[14,460,396],{},", or ",[14,463,464],{},"contextlib.AsyncExitStack",", which gives the same register-as-you-go semantics.",[29,467,469],{"id":468},"exitstack-the-same-idea-without-pytest","ExitStack: the same idea without pytest",[10,471,472,475,476,478],{},[14,473,474],{},"contextlib.ExitStack"," offers register-as-you-go cleanup as a plain Python object, which makes it a useful middle ground: the fixture keeps the readability of ",[14,477,16],{}," while each resource's cleanup is registered as soon as it exists.",[67,480,482],{"className":69,"code":481,"language":71,"meta":72,"style":72},"import contextlib\n\nimport pytest\n\n\n@pytest.fixture\ndef seeded_environment(storage, queue, database):\n    with contextlib.ExitStack() as stack:\n        bucket = storage.create_bucket(\"seed\")\n        stack.callback(storage.delete_bucket, bucket)\n\n        subscription = queue.subscribe(\"events\")\n        stack.callback(subscription.cancel)\n\n        schema = database.create_schema(\"seed_schema\")\n        stack.callback(database.drop_schema, schema)\n\n        yield {\"bucket\": bucket, \"subscription\": subscription, \"schema\": schema}\n    # Leaving the with-block unwinds every registered callback in reverse order.\n",[14,483,484,489,493,497,501,505,509,514,519,524,529,533,538,543,547,552,557,561,566],{"__ignoreMap":72},[76,485,486],{"class":78,"line":79},[76,487,488],{},"import contextlib\n",[76,490,491],{"class":78,"line":85},[76,492,89],{"emptyLinePlaceholder":88},[76,494,495],{"class":78,"line":92},[76,496,82],{},[76,498,499],{"class":78,"line":97},[76,500,89],{"emptyLinePlaceholder":88},[76,502,503],{"class":78,"line":103},[76,504,89],{"emptyLinePlaceholder":88},[76,506,507],{"class":78,"line":109},[76,508,100],{},[76,510,511],{"class":78,"line":115},[76,512,513],{},"def seeded_environment(storage, queue, database):\n",[76,515,516],{"class":78,"line":121},[76,517,518],{},"    with contextlib.ExitStack() as stack:\n",[76,520,521],{"class":78,"line":127},[76,522,523],{},"        bucket = storage.create_bucket(\"seed\")\n",[76,525,526],{"class":78,"line":133},[76,527,528],{},"        stack.callback(storage.delete_bucket, bucket)\n",[76,530,531],{"class":78,"line":138},[76,532,89],{"emptyLinePlaceholder":88},[76,534,535],{"class":78,"line":143},[76,536,537],{},"        subscription = queue.subscribe(\"events\")\n",[76,539,540],{"class":78,"line":148},[76,541,542],{},"        stack.callback(subscription.cancel)\n",[76,544,545],{"class":78,"line":154},[76,546,89],{"emptyLinePlaceholder":88},[76,548,549],{"class":78,"line":160},[76,550,551],{},"        schema = database.create_schema(\"seed_schema\")\n",[76,553,554],{"class":78,"line":166},[76,555,556],{},"        stack.callback(database.drop_schema, schema)\n",[76,558,559],{"class":78,"line":172},[76,560,89],{"emptyLinePlaceholder":88},[76,562,563],{"class":78,"line":178},[76,564,565],{},"        yield {\"bucket\": bucket, \"subscription\": subscription, \"schema\": schema}\n",[76,567,568],{"class":78,"line":184},[76,569,570],{},"    # Leaving the with-block unwinds every registered callback in reverse order.\n",[10,572,229,573,576,577,580,581,584,585,587,588,590,591,436,594,51],{},[14,574,575],{},"create_schema"," raises inside the ",[14,578,579],{},"with"," block, ",[14,582,583],{},"ExitStack.__exit__"," still runs the two callbacks already registered. The fixture reads top to bottom like the ",[14,586,16],{}," version and cleans up like the ",[14,589,64],{}," version, and the same pattern works for async fixtures with ",[14,592,593],{},"AsyncExitStack",[14,595,596],{},"push_async_callback",[241,598,600,694],{"className":599},[244],[246,601,254,606,254,609,254,612,254,616,254,621,254,628,254,631,254,635,254,640,254,644,254,649,254,653,254,656,254,658,254,660,254,664,254,667,254,670,254,673,254,676,254,678,254,682,254,686,254,689,254,691],{"viewBox":602,"role":249,"ariaLabelledBy":603,"xmlns":253},"0 0 800 234",[604,605],"es-t","es-d",[256,607,608],{"id":604},"Three ways to express register-as-you-go cleanup",[260,610,611],{"id":605},"Three options compared. A single yield is clearest but only safe for one resource. request.addfinalizer registers each cleanup as it goes and works for any number of resources but only with synchronous callables. ExitStack combines the yield layout with per-resource registration and has an async counterpart.",[264,613],{"x":266,"y":266,"width":614,"height":615,"rx":269,"fill":270},"800","234",[272,617,620],{"x":618,"y":275,"textAnchor":276,"fontSize":619,"fontWeight":278,"fill":279},"400","15.5","Choose by the number of resources and whether cleanup is async",[264,622],{"x":623,"y":624,"width":625,"height":626,"rx":287,"fill":270,"stroke":627,"strokeWidth":290},"24","50","240","164","#f2cc8f",[264,629],{"x":623,"y":624,"width":625,"height":630,"rx":287,"fill":279},"30",[272,632,16],{"x":633,"y":634,"textAnchor":276,"fontSize":287,"fontWeight":278,"fill":270},"144","70",[272,636,639],{"x":637,"y":638,"fontSize":301,"fill":279},"40","104","one resource, one step",[272,641,643],{"x":637,"y":642,"fontSize":301,"fill":279},"126","clearest to read",[272,645,648],{"x":637,"y":646,"fontSize":301,"fontWeight":278,"fill":647},"162","#8a5a00","leaks on partial setup",[272,650,652],{"x":637,"y":651,"fontSize":301,"fill":279},"184","with several steps",[264,654],{"x":655,"y":624,"width":625,"height":626,"rx":287,"fill":270,"stroke":329,"strokeWidth":290},"280",[264,657],{"x":655,"y":624,"width":625,"height":630,"rx":287,"fill":279},[272,659,64],{"x":618,"y":634,"textAnchor":276,"fontSize":287,"fontWeight":278,"fill":270},[272,661,663],{"x":662,"y":638,"fontSize":301,"fill":279},"296","any number of steps",[272,665,666],{"x":662,"y":642,"fontSize":301,"fill":279},"registered as acquired",[272,668,669],{"x":662,"y":646,"fontSize":301,"fontWeight":278,"fill":302},"safe on partial setup",[272,671,672],{"x":662,"y":651,"fontSize":301,"fill":279},"sync callables only",[264,674],{"x":675,"y":624,"width":625,"height":626,"rx":287,"fill":270,"stroke":329,"strokeWidth":290},"536",[264,677],{"x":675,"y":624,"width":625,"height":630,"rx":287,"fill":279},[272,679,681],{"x":680,"y":634,"textAnchor":276,"fontSize":287,"fontWeight":278,"fill":270},"656","ExitStack + yield",[272,683,685],{"x":684,"y":638,"fontSize":301,"fill":279},"552","yield's layout",[272,687,688],{"x":684,"y":642,"fontSize":301,"fill":279},"per-resource callbacks",[272,690,669],{"x":684,"y":646,"fontSize":301,"fontWeight":278,"fill":302},[272,692,693],{"x":684,"y":651,"fontSize":301,"fill":279},"AsyncExitStack for async",[353,695,696],{},"For async fixtures the right-hand column is the only one of the three that gives register-as-you-go cleanup, which makes it the default choice there.",[29,698,700],{"id":699},"teardown-order-across-fixtures","Teardown order across fixtures",[10,702,703],{},"Within one fixture the order is the reverse of registration. Across fixtures, pytest tears down in the reverse of the order it set them up, and that order is decided by the dependency graph: a fixture that requests another is set up after it and torn down before it. Getting this right matters whenever one resource depends on another — a connection on a server, a subscription on a broker, a schema on a database.",[10,705,706,707,711],{},"The rule that follows is simple and worth stating plainly. If resource B cannot be cleaned up without resource A still existing, the fixture that provides B must ",[708,709,710],"em",{},"request"," the fixture that provides A. Declaring the dependency explicitly is what guarantees B's teardown runs while A is still alive. Two fixtures that happen to be set up in the right order because the test listed them in that order have no such guarantee, and the order can change the moment another test requests them differently.",[10,713,714,715,718,719,722,723,725,726,728,729,731],{},"A common trap illustrates it. A ",[14,716,717],{},"client"," fixture and a ",[14,720,721],{},"server"," fixture, both requested by a test but with no dependency between them, are set up in the order the test lists them and torn down in reverse. List ",[14,724,717],{}," first and the server tears down before the client — which then fails to close its connection cleanly and reports a teardown error that looks like a bug in the client. Making ",[14,727,717],{}," request ",[14,730,721],{}," fixes it permanently, because now the graph, not the test's argument order, decides the sequence.",[10,733,734,735,738,739,742],{},"The same principle extends across scopes. A function-scoped fixture that depends on a session-scoped one is always torn down first, because function teardown happens at the end of each test while session teardown happens once at the very end of the run. That ordering is fixed by pytest and needs no declaration — but it does mean a session-scoped resource must never depend on anything narrower, which is exactly the rule ",[14,736,737],{},"ScopeMismatch"," enforces. When a teardown error appears only on the last test of a session, a session fixture reaching for something a narrower scope already released is the first thing to check, and ",[14,740,741],{},"--setup-show"," will display the offending order directly.",[241,744,746,812],{"className":745},[244],[246,747,254,752,254,755,254,758,254,761,254,764,254,768,254,773,254,776,254,779,254,783,254,786,254,789,254,792,254,796,254,800,254,803,254,806,254,809],{"viewBox":748,"role":249,"ariaLabelledBy":749,"xmlns":253},"0 0 800 244",[750,751],"ord2-t","ord2-d",[256,753,754],{"id":750},"Teardown order follows the dependency graph",[260,756,757],{"id":751},"Two arrangements of a server and a client fixture. Without a declared dependency, teardown order follows the order the test happened to list them, and the server can be torn down before the client. When the client fixture requests the server fixture, the graph guarantees the client tears down first while the server is still alive.",[264,759],{"x":266,"y":266,"width":614,"height":760,"rx":269,"fill":270},"244",[272,762,763],{"x":618,"y":275,"textAnchor":276,"fontSize":619,"fontWeight":278,"fill":279},"Declare the dependency; do not rely on argument order",[264,765],{"x":283,"y":624,"width":766,"height":767,"rx":287,"fill":288,"stroke":289,"strokeWidth":290},"360","174",[272,769,772],{"x":770,"y":771,"textAnchor":276,"fontSize":295,"fontWeight":278,"fill":279},"206","76","independent fixtures",[272,774,775],{"x":299,"y":638,"fontSize":301,"fill":279},"def test_x(client, server)",[272,777,778],{"x":299,"y":642,"fontSize":301,"fill":279},"setup: client, then server",[272,780,782],{"x":299,"y":781,"fontSize":301,"fill":279},"148","teardown: server, then client",[272,784,785],{"x":299,"y":651,"fontSize":301,"fontWeight":278,"fill":311},"client closes against a dead server",[272,787,788],{"x":299,"y":770,"fontSize":301,"fill":279},"teardown error blamed on client",[264,790],{"x":791,"y":624,"width":766,"height":767,"rx":287,"fill":328,"stroke":329,"strokeWidth":290},"414",[272,793,795],{"x":794,"y":771,"textAnchor":276,"fontSize":295,"fontWeight":278,"fill":279},"594","client requests server",[272,797,799],{"x":798,"y":638,"fontSize":301,"fill":279},"432","def client(server): …",[272,801,802],{"x":798,"y":642,"fontSize":301,"fill":279},"setup: server, then client",[272,804,805],{"x":798,"y":781,"fontSize":301,"fill":279},"teardown: client, then server",[272,807,808],{"x":798,"y":651,"fontSize":301,"fontWeight":278,"fill":302},"guaranteed by the graph",[272,810,811],{"x":798,"y":770,"fontSize":301,"fill":279},"argument order no longer matters",[353,813,814],{},"Teardown errors that appear or disappear when a test's argument list is reordered are always this: a dependency that exists in reality but not in the fixture graph.",[29,816,818],{"id":817},"checking-teardown-actually-happens","Checking teardown actually happens",[10,820,821,822,825],{},"Teardown bugs are silent by nature: a leaked resource does not fail the test that leaked it. ",[14,823,824],{},"pytest --setup-show"," makes setup and teardown visible, printing each fixture's setup and teardown in order with its scope, which is the fastest way to confirm that a fixture's cleanup runs when and where you expect.",[10,827,828],{},"For resources outside the process — buckets, schemas, containers — the stronger check is a session-scoped fixture that records what exists at the start of the run and asserts nothing extra exists at the end. It turns a slow accumulation of leaked resources, usually noticed weeks later as a quota error, into a failure on the run that introduced the leak. Combined with deliberately failing a setup step once and confirming the partial resources were released, it gives real confidence that the fixture behaves correctly in the case that matters most — which is also the case least likely to be exercised by an ordinary green run.",[29,830,832],{"id":831},"frequently-asked-questions","Frequently Asked Questions",[10,834,835,838,839,841,842,844,845,847],{},[389,836,837],{},"Does code after yield run if the test fails?","\nYes. Teardown after ",[14,840,16],{}," runs whether the test passed, failed or raised. It does not run if the fixture itself raised before reaching ",[14,843,16],{},", because pytest never considered the fixture set up — which is the case ",[14,846,64],{}," handles better.",[10,849,850,853],{},[389,851,852],{},"In what order do finalizers run?","\nIn reverse order of registration, and fixtures tear down in reverse order of setup. That mirrors how resources are usually nested: the last thing acquired is the first thing released.",[10,855,856,859,860,862],{},[389,857,858],{},"What happens if teardown raises?","\npytest reports it as an error in the teardown phase, separate from the test's own outcome, and continues tearing down other fixtures. With several ",[14,861,64],{}," callbacks, one raising does not prevent the others from running.",[29,864,866],{"id":865},"related","Related",[34,868,869,875,882,889],{},[37,870,871,874],{},[47,872,873],{"href":49},"Mastering pytest Fixtures"," — scopes and the dependency graph these teardowns run within.",[37,876,877,881],{},[47,878,880],{"href":879},"\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Fparametrizing-fixtures-with-params-and-ids\u002F","Parametrizing Fixtures with params and ids"," — teardown runs once per parameter.",[37,883,884,888],{},[47,885,887],{"href":886},"\u002Ftesting-async-and-concurrent-python\u002Fpytest-asyncio-in-depth\u002Ftesting-async-generators-and-context-managers\u002F","Testing Async Generators and Context Managers"," — the async equivalent of these cleanup rules.",[37,890,891,895],{},[47,892,894],{"href":893},"\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Ffixing-scopemismatch-errors-in-pytest\u002F","Fixing ScopeMismatch Errors in pytest"," — the other scoping mistake that shows up at teardown.",[10,897,898,899],{},"← Back to ",[47,900,873],{"href":49},[902,903,904],"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":72,"searchDepth":85,"depth":85,"links":906},[907,908,909,910,911,912,913,914,915],{"id":31,"depth":85,"text":32},{"id":54,"depth":85,"text":55},{"id":358,"depth":85,"text":359},{"id":382,"depth":85,"text":383},{"id":468,"depth":85,"text":469},{"id":699,"depth":85,"text":700},{"id":817,"depth":85,"text":818},{"id":831,"depth":85,"text":832},{"id":865,"depth":85,"text":866},"When to use a yield fixture and when request.addfinalizer is the better teardown tool: partial setup failure, multiple resources, ordering, and exceptions during cleanup.","md",{"slug":919,"type":920,"breadcrumb":921,"datePublished":922,"dateModified":922,"faq":923,"howto":930},"fixture-teardown-with-yield-vs-addfinalizer","article","yield vs addfinalizer","2026-09-18",[924,926,928],{"q":837,"a":925},"Yes. Teardown after yield runs whether the test passed, failed or raised. It does not run if the fixture itself raised before reaching yield, because pytest never considered the fixture set up — which is the case addfinalizer handles better.",{"q":852,"a":927},"In reverse order of registration, and fixtures tear down in reverse order of setup. That mirrors how resources are usually nested: the last thing acquired is the first thing released.",{"q":858,"a":929},"pytest reports it as an error in the teardown phase, separate from the test's own outcome, and continues tearing down other fixtures. With several addfinalizer callbacks, one raising does not prevent the others from running.",{"name":931,"description":932,"steps":933},"How to choose between yield and addfinalizer for teardown","Use yield for one resource acquired in one step, and addfinalizer when setup acquires several resources that must each be released even if later steps fail.",[934,937,940,943,946],{"name":935,"text":936},"Default to yield for single resources","Acquire, yield, release — wrapped in try\u002Ffinally only if the release must run after a failing test body that you are also asserting on.",{"name":938,"text":939},"Switch to addfinalizer for multi-step setup","Register each resource's cleanup immediately after acquiring it, so a failure in a later step still releases the earlier ones.",{"name":941,"text":942},"Keep teardown idempotent","Make release safe to call on a resource that was only partly initialised.",{"name":944,"text":945},"Let teardown errors surface","Do not swallow exceptions in cleanup; pytest reports them as teardown errors, which is the signal you want.",{"name":947,"text":948},"Verify with setup-show","Run pytest --setup-show to confirm setup and teardown order matches your expectations.","\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Ffixture-teardown-with-yield-vs-addfinalizer",{"title":5,"description":916},"advanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Ffixture-teardown-with-yield-vs-addfinalizer\u002Findex","UYzSBviMGaNPm9I8KwF-TPwECYKNvAGL6QQXRbEbvsg",1789718767397]