[{"data":1,"prerenderedAt":857},["ShallowReactive",2],{"page-\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002Fcombining-given-with-pytest-fixtures-safely\u002F":3},{"id":4,"title":5,"body":6,"description":820,"extension":821,"meta":822,"navigation":83,"path":853,"seo":854,"stem":855,"__hash__":856},"content\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002Fcombining-given-with-pytest-fixtures-safely\u002Findex.md","Combining @given with pytest Fixtures Safely",{"type":7,"value":8,"toc":809},"minimark",[9,17,24,29,51,55,172,183,238,326,330,336,339,343,386,390,393,466,473,483,563,567,576,636,650,660,724,728,736,739,742,746,752,758,767,771,800,805],[10,11,12,16],"p",{},[13,14,15],"code",{},"@given"," and pytest fixtures look like they compose trivially — the fixture arguments and the generated arguments sit side by side in the signature — and in the common case they do. The trap is lifetime. pytest sets up a function-scoped fixture once per test function; Hypothesis runs the body once per generated example, which might be two hundred times. A fixture that returns a mutable object therefore shares one instance across every example, and state left by example 17 is visible to example 18.",[10,18,19,20,23],{},"Hypothesis detects this and raises ",[13,21,22],{},"HealthCheck.function_scoped_fixture",". The check is not pedantry: a property test whose examples share mutable state can pass because of an accident of ordering and fail on a different seed, which is exactly the flakiness property testing is supposed to remove. The fix is to be deliberate about what fixtures provide and what the test body creates. In practice that means sorting every fixture a property test uses into one of three kinds: immutable configuration, which is safe to share; an expensive shared resource, which is safe to share if the body resets it per example; and mutable per-test state, which belongs in the body. Once the fixtures are sorted, the right code for each is obvious, and the health check becomes a useful confirmation rather than an obstacle.",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,32,43],"ul",{},[33,34,35,38,39,42],"li",{},[13,36,37],{},"hypothesis >= 6.100"," and ",[13,40,41],{},"pytest >= 8.0",".",[33,44,45,46,42],{},"The execution model from ",[47,48,50],"a",{"href":49},"\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002F","Hypothesis integration with pytest and frameworks",[25,52,54],{"id":53},"solution","Solution",[56,57,62],"pre",{"className":58,"code":59,"language":60,"meta":61,"style":61},"language-python shiki shiki-themes github-light github-dark","import pytest\nfrom hypothesis import HealthCheck, given, settings, strategies as st\n\n\n@pytest.fixture\ndef pricing_rules():\n    # Immutable configuration: safe to share across every example.\n    return PricingRules.load(\"tests\u002Ffixtures\u002Fpricing.yaml\")\n\n\n@given(items=st.lists(st.integers(min_value=1, max_value=10_000), max_size=20))\n@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])\ndef test_cart_total_is_never_negative(pricing_rules, items):\n    # Mutable state created INSIDE the body: fresh for every example.\n    cart = Cart(rules=pricing_rules)\n    for price in items:\n        cart.add(price)\n    assert cart.total() >= 0\n","python","",[13,63,64,72,78,85,90,96,102,108,114,119,124,130,136,142,148,154,160,166],{"__ignoreMap":61},[65,66,69],"span",{"class":67,"line":68},"line",1,[65,70,71],{},"import pytest\n",[65,73,75],{"class":67,"line":74},2,[65,76,77],{},"from hypothesis import HealthCheck, given, settings, strategies as st\n",[65,79,81],{"class":67,"line":80},3,[65,82,84],{"emptyLinePlaceholder":83},true,"\n",[65,86,88],{"class":67,"line":87},4,[65,89,84],{"emptyLinePlaceholder":83},[65,91,93],{"class":67,"line":92},5,[65,94,95],{},"@pytest.fixture\n",[65,97,99],{"class":67,"line":98},6,[65,100,101],{},"def pricing_rules():\n",[65,103,105],{"class":67,"line":104},7,[65,106,107],{},"    # Immutable configuration: safe to share across every example.\n",[65,109,111],{"class":67,"line":110},8,[65,112,113],{},"    return PricingRules.load(\"tests\u002Ffixtures\u002Fpricing.yaml\")\n",[65,115,117],{"class":67,"line":116},9,[65,118,84],{"emptyLinePlaceholder":83},[65,120,122],{"class":67,"line":121},10,[65,123,84],{"emptyLinePlaceholder":83},[65,125,127],{"class":67,"line":126},11,[65,128,129],{},"@given(items=st.lists(st.integers(min_value=1, max_value=10_000), max_size=20))\n",[65,131,133],{"class":67,"line":132},12,[65,134,135],{},"@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])\n",[65,137,139],{"class":67,"line":138},13,[65,140,141],{},"def test_cart_total_is_never_negative(pricing_rules, items):\n",[65,143,145],{"class":67,"line":144},14,[65,146,147],{},"    # Mutable state created INSIDE the body: fresh for every example.\n",[65,149,151],{"class":67,"line":150},15,[65,152,153],{},"    cart = Cart(rules=pricing_rules)\n",[65,155,157],{"class":67,"line":156},16,[65,158,159],{},"    for price in items:\n",[65,161,163],{"class":67,"line":162},17,[65,164,165],{},"        cart.add(price)\n",[65,167,169],{"class":67,"line":168},18,[65,170,171],{},"    assert cart.total() >= 0\n",[10,173,174,175,178,179,182],{},"The suppression is justified here, and the comment on the fixture says why: ",[13,176,177],{},"pricing_rules"," is immutable, so sharing it across examples cannot leak state. The ",[13,180,181],{},"Cart",", which is mutated, is built in the body and is therefore new for every example.",[56,184,186],{"className":58,"code":185,"language":60,"meta":61,"style":61},"# The unsafe version the health check exists to catch:\n@pytest.fixture\ndef cart(pricing_rules):\n    return Cart(rules=pricing_rules)          # ONE cart for all examples\n\n\n@given(items=st.lists(st.integers(min_value=1, max_value=10_000)))\ndef test_total_matches_items(cart, items):    # health check fires, rightly\n    for price in items:\n        cart.add(price)\n    assert cart.total() == sum(items)         # fails from example 2 onward\n",[13,187,188,193,197,202,207,211,215,220,225,229,233],{"__ignoreMap":61},[65,189,190],{"class":67,"line":68},[65,191,192],{},"# The unsafe version the health check exists to catch:\n",[65,194,195],{"class":67,"line":74},[65,196,95],{},[65,198,199],{"class":67,"line":80},[65,200,201],{},"def cart(pricing_rules):\n",[65,203,204],{"class":67,"line":87},[65,205,206],{},"    return Cart(rules=pricing_rules)          # ONE cart for all examples\n",[65,208,209],{"class":67,"line":92},[65,210,84],{"emptyLinePlaceholder":83},[65,212,213],{"class":67,"line":98},[65,214,84],{"emptyLinePlaceholder":83},[65,216,217],{"class":67,"line":104},[65,218,219],{},"@given(items=st.lists(st.integers(min_value=1, max_value=10_000)))\n",[65,221,222],{"class":67,"line":110},[65,223,224],{},"def test_total_matches_items(cart, items):    # health check fires, rightly\n",[65,226,227],{"class":67,"line":116},[65,228,159],{},[65,230,231],{"class":67,"line":121},[65,232,165],{},[65,234,235],{"class":67,"line":126},[65,236,237],{},"    assert cart.total() == sum(items)         # fails from example 2 onward\n",[239,240,243,322],"figure",{"className":241},[242],"diagram",[244,245,252,253,252,257,252,261,252,269,252,279,252,289,252,294,252,299,252,304,252,309,252,313,252,317],"svg",{"viewBox":246,"role":247,"ariaLabelledBy":248,"xmlns":251},"0 0 820 262","img",[249,250],"gf-t","gf-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[254,255,256],"title",{"id":249},"Fixture lifetime versus example lifetime",[258,259,260],"desc",{"id":250},"A function-scoped fixture is set up once, then the test body runs for example one, example two and example three. A mutable cart provided by the fixture accumulates items across all three examples. A cart created inside the body is new for each example, so every example starts clean.",[262,263],"rect",{"x":264,"y":264,"width":265,"height":266,"rx":267,"fill":268},"0","820","262","14","#fffdf8",[270,271,278],"text",{"x":272,"y":273,"textAnchor":274,"fontSize":275,"fontWeight":276,"fill":277},"410","28","middle","16","700","#3d405b","One fixture, many examples",[262,280],{"x":281,"y":282,"width":283,"height":284,"rx":285,"fill":286,"stroke":287,"strokeWidth":288},"26","52","768","80","12","#fbe9e3","#e07a5f","2",[270,290,293],{"x":291,"y":292,"fontSize":285,"fontWeight":276,"fill":277},"46","76","cart from a fixture",[270,295,298],{"x":291,"y":296,"fontSize":297,"fill":277},"100","11","example 1 adds [5] → example 2 sees [5] and adds [9] → example 3 sees [5, 9] …",[270,300,303],{"x":291,"y":301,"fontSize":297,"fill":302},"120","#8f3d22","every example after the first runs against dirty state",[262,305],{"x":281,"y":306,"width":283,"height":284,"rx":285,"fill":307,"stroke":308,"strokeWidth":288},"146","#e6f0ea","#81b29a",[270,310,312],{"x":291,"y":311,"fontSize":285,"fontWeight":276,"fill":277},"170","cart built in the body",[270,314,316],{"x":291,"y":315,"fontSize":297,"fill":277},"194","example 1: new cart · example 2: new cart · example 3: new cart",[270,318,321],{"x":291,"y":319,"fontSize":297,"fill":320},"214","#2a5f49","every example is independent, so shrinking and replay work",[323,324,325],"figcaption",{},"The health check fires on the upper row. Moving construction into the body turns it into the lower row with no other change.",[25,327,329],{"id":328},"why-this-works","Why this works",[10,331,332,333,335],{},"pytest resolves fixtures before calling the test function, and Hypothesis's ",[13,334,15],{}," wraps that function so the call pytest makes becomes a loop over generated examples. Fixtures are outside the loop; the body is inside it. Anything constructed in the body is therefore per-example, and anything supplied by a function-scoped fixture is per-test-function — shared by every example in the loop.",[10,337,338],{},"Isolation matters for more than correctness. Hypothesis shrinks a failure by replaying variations of the failing example, and replays assume each example's outcome depends only on its own inputs. Shared mutable state breaks that assumption: a shrunk example may pass because the state it depended on was left by an example that is no longer being run, and the shrinker reports a confusing or non-reproducible counterexample.",[25,340,342],{"id":341},"edge-cases-and-failure-modes","Edge cases and failure modes",[30,344,345,352,358,364,370],{},[33,346,347,351],{},[348,349,350],"strong",{},"Suppressing the check globally."," Adding it to a settings profile silences the one warning that catches leaking state. Suppress per test, with a reason.",[33,353,354,357],{},[348,355,356],{},"Mutable defaults in immutable-looking fixtures."," A configuration object with a mutable dict attribute is not immutable. Freeze it, or build it in the body.",[33,359,360,363],{},[348,361,362],{},"Database sessions."," A shared session accumulates rows across examples. Open a savepoint at the start of each example and roll it back at the end, inside the body.",[33,365,366,369],{},[348,367,368],{},"Fixtures with teardown."," Teardown runs once after all examples, not after each. Resources acquired per example must be released per example, in the body.",[33,371,372,375,376,38,379,382,383,385],{},[348,373,374],{},"Dependent generation."," Drawing a value that depends on fixture data needs ",[13,377,378],{},"st.data()",[13,380,381],{},"data.draw(...)"," in the body, since strategies in ",[13,384,15],{}," are built before fixtures exist.",[25,387,389],{"id":388},"sharing-an-expensive-resource-correctly","Sharing an expensive resource correctly",[10,391,392],{},"Some resources are genuinely expensive and must be shared — a database connection, a compiled model, a started server — while each example still needs a clean slate. The pattern is a shared resource from a fixture plus a per-example reset inside the body.",[56,394,396],{"className":58,"code":395,"language":60,"meta":61,"style":61},"from hypothesis import HealthCheck, given, settings\n\n\n@settings(suppress_health_check=[HealthCheck.function_scoped_fixture],\n          max_examples=50, deadline=None)\n@given(order=orders())\ndef test_saved_order_round_trips(db_session, order):\n    savepoint = db_session.begin_nested()       # per-example isolation\n    try:\n        repo = SqlOrderRepository(db_session)\n        repo.add(order)\n        assert repo.get(order.id) == order\n    finally:\n        savepoint.rollback()                    # nothing survives to the next example\n",[13,397,398,403,407,411,416,421,426,431,436,441,446,451,456,461],{"__ignoreMap":61},[65,399,400],{"class":67,"line":68},[65,401,402],{},"from hypothesis import HealthCheck, given, settings\n",[65,404,405],{"class":67,"line":74},[65,406,84],{"emptyLinePlaceholder":83},[65,408,409],{"class":67,"line":80},[65,410,84],{"emptyLinePlaceholder":83},[65,412,413],{"class":67,"line":87},[65,414,415],{},"@settings(suppress_health_check=[HealthCheck.function_scoped_fixture],\n",[65,417,418],{"class":67,"line":92},[65,419,420],{},"          max_examples=50, deadline=None)\n",[65,422,423],{"class":67,"line":98},[65,424,425],{},"@given(order=orders())\n",[65,427,428],{"class":67,"line":104},[65,429,430],{},"def test_saved_order_round_trips(db_session, order):\n",[65,432,433],{"class":67,"line":110},[65,434,435],{},"    savepoint = db_session.begin_nested()       # per-example isolation\n",[65,437,438],{"class":67,"line":116},[65,439,440],{},"    try:\n",[65,442,443],{"class":67,"line":121},[65,444,445],{},"        repo = SqlOrderRepository(db_session)\n",[65,447,448],{"class":67,"line":126},[65,449,450],{},"        repo.add(order)\n",[65,452,453],{"class":67,"line":132},[65,454,455],{},"        assert repo.get(order.id) == order\n",[65,457,458],{"class":67,"line":138},[65,459,460],{},"    finally:\n",[65,462,463],{"class":67,"line":144},[65,464,465],{},"        savepoint.rollback()                    # nothing survives to the next example\n",[10,467,468,469,472],{},"The fixture provides the connection once; the savepoint gives each example a clean database; the ",[13,470,471],{},"finally"," guarantees the rollback even when the assertion fails and Hypothesis moves on to shrinking. The suppression is justified by the savepoint, and a short comment saying so keeps a future reader from removing either.",[10,474,475,478,479,482],{},[13,476,477],{},"deadline=None"," is worth noting too. Database round trips vary in latency, and the default per-example deadline would flag a slow example as a failure even though the property holds. For properties involving I\u002FO, disabling the deadline and bounding ",[13,480,481],{},"max_examples"," instead keeps the test both reliable and affordable.",[239,484,486,560],{"className":485},[242],[244,487,252,492,252,495,252,498,252,502,252,507,252,514,252,518,252,524,252,530,252,534,252,537,252,540,252,543,252,545,252,547,252,550,252,554,252,557],{"viewBox":488,"role":247,"ariaLabelledBy":489,"xmlns":251},"0 0 800 236",[490,491],"sv-t","sv-d",[254,493,494],{"id":490},"A shared connection with per-example savepoints",[258,496,497],{"id":491},"A database session fixture is created once for the test function. Inside the body, each generated example opens a savepoint, runs its operations and rolls back in a finally block, so examples share the expensive connection but none sees another's rows.",[262,499],{"x":264,"y":264,"width":500,"height":501,"rx":267,"fill":268},"800","236",[270,503,506],{"x":504,"y":273,"textAnchor":274,"fontSize":505,"fontWeight":276,"fill":277},"400","15.5","Share the expensive part, reset the rest",[262,508],{"x":281,"y":509,"width":510,"height":511,"rx":285,"fill":512,"stroke":277,"strokeWidth":513},"50","748","166","#f4f1de","1.8",[270,515,517],{"x":291,"y":516,"fontSize":285,"fontWeight":276,"fill":277},"74","db_session fixture — one connection for the test function",[262,519],{"x":509,"y":520,"width":521,"height":522,"rx":523,"fill":307,"stroke":308,"strokeWidth":513},"92","220","104","10",[270,525,529],{"x":526,"y":527,"textAnchor":274,"fontSize":528,"fontWeight":276,"fill":277},"160","118","11.5","example 1",[270,531,533],{"x":526,"y":532,"textAnchor":274,"fontSize":297,"fill":277},"142","SAVEPOINT · add · get",[270,535,536],{"x":526,"y":511,"textAnchor":274,"fontSize":297,"fill":320},"ROLLBACK TO",[262,538],{"x":539,"y":520,"width":521,"height":522,"rx":523,"fill":307,"stroke":308,"strokeWidth":513},"290",[270,541,542],{"x":504,"y":527,"textAnchor":274,"fontSize":528,"fontWeight":276,"fill":277},"example 2",[270,544,533],{"x":504,"y":532,"textAnchor":274,"fontSize":297,"fill":277},[270,546,536],{"x":504,"y":511,"textAnchor":274,"fontSize":297,"fill":320},[262,548],{"x":549,"y":520,"width":521,"height":522,"rx":523,"fill":307,"stroke":308,"strokeWidth":513},"530",[270,551,553],{"x":552,"y":527,"textAnchor":274,"fontSize":528,"fontWeight":276,"fill":277},"640","example 3 …",[270,555,556],{"x":552,"y":532,"textAnchor":274,"fontSize":297,"fill":277},"clean database again",[270,558,559],{"x":552,"y":511,"textAnchor":274,"fontSize":297,"fill":320},"shrinking replays safely",[323,561,562],{},"The connection is paid for once; every example still starts from the same state, which is what makes shrinking and replay trustworthy.",[25,564,566],{"id":565},"drawing-values-that-depend-on-fixtures","Drawing values that depend on fixtures",[10,568,569,570,572,573,575],{},"Strategies passed to ",[13,571,15],{}," are built before pytest resolves fixtures, so they cannot refer to fixture values. When generation needs to depend on a fixture — pick an existing customer id from a seeded database, choose a product from a loaded catalogue — ",[13,574,378],{}," moves the draw into the test body, where the fixture is available.",[56,577,579],{"className":58,"code":578,"language":60,"meta":61,"style":61},"from hypothesis import HealthCheck, given, settings, strategies as st\n\n\n@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])\n@given(data=st.data())\ndef test_discount_applies_to_any_catalogue_item(catalogue, data):\n    # catalogue is an immutable fixture; the draw uses its contents.\n    sku = data.draw(st.sampled_from(sorted(catalogue.skus)), label=\"sku\")\n    quantity = data.draw(st.integers(min_value=1, max_value=50), label=\"quantity\")\n\n    price = catalogue.price(sku, quantity, discount_code=\"TEN\")\n    assert price \u003C= catalogue.price(sku, quantity)\n",[13,580,581,585,589,593,597,602,607,612,617,622,626,631],{"__ignoreMap":61},[65,582,583],{"class":67,"line":68},[65,584,77],{},[65,586,587],{"class":67,"line":74},[65,588,84],{"emptyLinePlaceholder":83},[65,590,591],{"class":67,"line":80},[65,592,84],{"emptyLinePlaceholder":83},[65,594,595],{"class":67,"line":87},[65,596,135],{},[65,598,599],{"class":67,"line":92},[65,600,601],{},"@given(data=st.data())\n",[65,603,604],{"class":67,"line":98},[65,605,606],{},"def test_discount_applies_to_any_catalogue_item(catalogue, data):\n",[65,608,609],{"class":67,"line":104},[65,610,611],{},"    # catalogue is an immutable fixture; the draw uses its contents.\n",[65,613,614],{"class":67,"line":110},[65,615,616],{},"    sku = data.draw(st.sampled_from(sorted(catalogue.skus)), label=\"sku\")\n",[65,618,619],{"class":67,"line":116},[65,620,621],{},"    quantity = data.draw(st.integers(min_value=1, max_value=50), label=\"quantity\")\n",[65,623,624],{"class":67,"line":121},[65,625,84],{"emptyLinePlaceholder":83},[65,627,628],{"class":67,"line":126},[65,629,630],{},"    price = catalogue.price(sku, quantity, discount_code=\"TEN\")\n",[65,632,633],{"class":67,"line":132},[65,634,635],{},"    assert price \u003C= catalogue.price(sku, quantity)\n",[10,637,638,639,642,643,646,647,649],{},"The ",[13,640,641],{},"label"," arguments matter when a failure is reported: Hypothesis prints each interactive draw with its label, so the counterexample reads ",[13,644,645],{},"sku='SKU-7', quantity=1"," rather than two anonymous values. Draws made through ",[13,648,378],{}," shrink just like ordinary arguments, so the reported case is still minimal.",[10,651,652,653,656,657,659],{},"The pattern also solves the opposite problem — generated data that later draws depend on. Draw an order first, then draw a line index from ",[13,654,655],{},"range(len(order.lines))","; the second draw is constrained by the first, which a static ",[13,658,15],{}," signature cannot express without a composite strategy.",[239,661,663,721],{"className":662},[242],[244,664,252,669,252,672,252,675,252,678,252,681,252,687,252,692,252,696,252,700,252,704,252,707,252,711,252,715,252,718],{"viewBox":665,"role":247,"ariaLabelledBy":666,"xmlns":251},"0 0 800 226",[667,668],"dd-t","dd-d",[254,670,671],{"id":667},"Static strategies versus interactive draws",[258,673,674],{"id":668},"Strategies in the given decorator are built at import time, before fixtures exist, so they cannot use fixture data. The data strategy defers drawing into the test body, where fixtures are available, and each labelled draw appears in the counterexample and shrinks normally.",[262,676],{"x":264,"y":264,"width":500,"height":677,"rx":267,"fill":268},"226",[270,679,680],{"x":504,"y":273,"textAnchor":274,"fontSize":505,"fontWeight":276,"fill":277},"Draw inside the body when the draw needs the fixture",[262,682],{"x":281,"y":509,"width":683,"height":684,"rx":285,"fill":685,"stroke":686,"strokeWidth":288},"360","156","#f7f0da","#f2cc8f",[270,688,691],{"x":689,"y":292,"textAnchor":274,"fontSize":690,"fontWeight":276,"fill":277},"206","12.5","@given(sku=…)",[270,693,695],{"x":694,"y":522,"fontSize":297,"fill":277},"44","built at import time",[270,697,699],{"x":694,"y":698,"fontSize":297,"fill":277},"126","no access to fixtures",[270,701,703],{"x":694,"y":311,"fontSize":297,"fontWeight":276,"fill":702},"#8a5a00","cannot sample the catalogue",[262,705],{"x":706,"y":509,"width":683,"height":684,"rx":285,"fill":307,"stroke":308,"strokeWidth":288},"414",[270,708,710],{"x":709,"y":292,"textAnchor":274,"fontSize":690,"fontWeight":276,"fill":277},"594","data.draw(…, label=)",[270,712,714],{"x":713,"y":522,"fontSize":297,"fill":277},"432","drawn in the body",[270,716,717],{"x":713,"y":698,"fontSize":297,"fill":277},"fixture values available",[270,719,720],{"x":713,"y":311,"fontSize":297,"fontWeight":276,"fill":320},"labelled, shrinks normally",[323,722,723],{},"Interactive draws cost nothing in shrinking quality; they only move the point at which generation happens.",[25,725,727],{"id":726},"widening-fixture-scope-instead","Widening fixture scope instead",[10,729,730,731,735],{},"A third option, sometimes the cleanest, is to make the shared resource explicitly session- or module-scoped. Hypothesis's health check targets ",[732,733,734],"em",{},"function","-scoped fixtures specifically, because those are the ones that look per-test but are actually per-function; a session-scoped fixture is honestly shared, and nobody reading the code expects it to be fresh for each example.",[10,737,738],{},"That makes wider scope a good fit for resources that are both expensive and naturally immutable — a loaded machine-learning model, a parsed schema, a compiled regular-expression set, a read-only reference dataset. Declaring them at session scope removes the health-check warning without any suppression, makes the sharing obvious to readers, and saves the setup cost across every test in the session rather than only across one function's examples.",[10,740,741],{},"It is the wrong fit for anything mutable. A session-scoped database session or cart would leak state not just between examples but between tests, which is a larger version of the same problem. The rule that ties the three options together is simple: share only what cannot change, reset anything that can, and make the choice visible in the code — through scope, through a per-example reset in the body, or through a suppression with a comment explaining why the state cannot leak.",[25,743,745],{"id":744},"frequently-asked-questions","Frequently Asked Questions",[10,747,748,751],{},[348,749,750],{},"Why does Hypothesis raise HealthCheck.function_scoped_fixture?","\nBecause a function-scoped fixture is created once for the whole test function, while the test body runs once per generated example. Any mutable state in the fixture is shared across all examples, so later examples see changes made by earlier ones. The health check flags that the examples are not isolated.",[10,753,754,757],{},[348,755,756],{},"Is it ever safe to suppress that health check?","\nYes, when the fixture provides something immutable or stateless — a configuration object, a pure function, a read-only client — or when the test body explicitly resets the state at the start of every example. Suppress it on that test only, with a comment saying why it is safe.",[10,759,760,763,764,766],{},[348,761,762],{},"Can I use @given on a fixture?","\nNo. Hypothesis drives test functions, not fixtures. If a fixture needs generated input, move the generation into the test with ",[13,765,378],{}," and draw inside the body, or build the object from drawn values in the test itself.",[25,768,770],{"id":769},"related","Related",[30,772,773,779,786,793],{},[33,774,775,778],{},[47,776,777],{"href":49},"Hypothesis Integration with pytest & Frameworks"," — the one-item-many-executions model.",[33,780,781,785],{},[47,782,784],{"href":783},"\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002Fproperty-testing-django-models-with-hypothesis\u002F","Property-Testing Django Models with Hypothesis"," — per-example transactions handled by the framework.",[33,787,788,792],{},[47,789,791],{"href":790},"\u002Fintegration-database-and-service-testing\u002Fdatabase-fixtures-and-transactional-tests\u002Frolling-back-every-test-with-nested-transactions\u002F","Rolling Back Every Test with Nested Transactions"," — the savepoint mechanism used above.",[33,794,795,799],{},[47,796,798],{"href":797},"\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-framework-fundamentals\u002Ffixing-hypothesis-flaky-health-check-failures\u002F","Fixing Hypothesis Flaky Health Check Failures"," — the other health checks and what they mean.",[10,801,802,803],{},"← Back to ",[47,804,777],{"href":49},[806,807,808],"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":61,"searchDepth":74,"depth":74,"links":810},[811,812,813,814,815,816,817,818,819],{"id":27,"depth":74,"text":28},{"id":53,"depth":74,"text":54},{"id":328,"depth":74,"text":329},{"id":341,"depth":74,"text":342},{"id":388,"depth":74,"text":389},{"id":565,"depth":74,"text":566},{"id":726,"depth":74,"text":727},{"id":744,"depth":74,"text":745},{"id":769,"depth":74,"text":770},"Use Hypothesis @given alongside pytest fixtures without leaking state between examples: the function-scoped fixture health check, per-example setup, and safe fixture scopes.","md",{"slug":823,"type":824,"breadcrumb":825,"datePublished":826,"dateModified":826,"faq":827,"howto":834},"combining-given-with-pytest-fixtures-safely","article","@given + Fixtures","2026-09-18",[828,830,832],{"q":750,"a":829},"Because a function-scoped fixture is created once for the whole test function, while the test body runs once per generated example. Any mutable state in the fixture is shared across all examples, so later examples see changes made by earlier ones. The health check flags that the examples are not isolated.",{"q":756,"a":831},"Yes, when the fixture provides something immutable or stateless — a configuration object, a pure function, a read-only client — or when the test body explicitly resets the state at the start of every example. Suppress it on that test only, with a comment saying why it is safe.",{"q":762,"a":833},"No. Hypothesis drives test functions, not fixtures. If a fixture needs generated input, move the generation into the test with st.data() and draw inside the body, or build the object from drawn values in the test itself.",{"name":835,"description":836,"steps":837},"How to combine Hypothesis with pytest fixtures safely","Keep fixtures to immutable or expensive shared resources, create per-example state in the test body, and suppress the health check only where isolation is guaranteed.",[838,841,844,847,850],{"name":839,"text":840},"Classify each fixture","Decide whether it provides immutable configuration, a shared expensive resource, or mutable per-test state.",{"name":842,"text":843},"Move mutable state into the body","Construct anything the test mutates inside the test function so each example gets a fresh copy.",{"name":845,"text":846},"Reset shared resources per example","For a shared database or cache, open a transaction or clear it at the start of the body.",{"name":848,"text":849},"Suppress the health check narrowly","Add suppress_health_check=[HealthCheck.function_scoped_fixture] only on tests where isolation is guaranteed.",{"name":851,"text":852},"Use st.data() for dependent draws","Draw values inside the body when they depend on fixture data.","\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002Fcombining-given-with-pytest-fixtures-safely",{"title":5,"description":820},"property-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002Fcombining-given-with-pytest-fixtures-safely\u002Findex","DfZaFQrq-KoiBFf47rr-ZHiP9NkfcvZ8U3aWIRASBFE",1789718768957]