[{"data":1,"prerenderedAt":1768},["ShallowReactive",2],{"page-\u002Fintegration-database-and-service-testing\u002Ftest-data-factories-and-builders\u002F":3},{"id":4,"title":5,"body":6,"description":1727,"extension":1728,"meta":1729,"navigation":229,"path":1764,"seo":1765,"stem":1766,"__hash__":1767},"content\u002Fintegration-database-and-service-testing\u002Ftest-data-factories-and-builders\u002Findex.md","Test Data Factories & Builders",{"type":7,"value":8,"toc":1707},"minimark",[9,18,23,60,64,67,198,202,207,306,320,324,372,375,379,494,514,525,529,626,646,660,664,711,718,722,725,768,813,816,820,929,933,938,1009,1029,1039,1045,1049,1052,1080,1090,1096,1206,1210,1229,1290,1293,1300,1304,1307,1319,1391,1398,1401,1404,1494,1501,1505,1508,1518,1531,1539,1549,1602,1605,1608,1611,1615,1621,1627,1639,1645,1657,1661,1696,1703],[10,11,12,13,17],"p",{},"Read a failing test and the first question is always the same: which of these twenty setup lines is the one the assertion depends on? Test data factories answer it by inverting the default. Instead of constructing every field explicitly, the test states only what makes this case different, and the factory supplies the rest. A test that reads ",[14,15,16],"code",{},"CustomerFactory(vat_exempt=True)"," tells the reader exactly what it is about; the same test with fifteen keyword arguments tells them nothing.",[19,20,22],"h2",{"id":21},"prerequisites","Prerequisites",[24,25,26,33,42,55],"ul",{},[27,28,29,32],"li",{},[14,30,31],{},"factory_boy >= 3.3",", or a hand-rolled builder — the patterns here apply to both.",[27,34,35,36,41],{},"A transactional database fixture, since factories must write inside the test's transaction: see ",[37,38,40],"a",{"href":39},"\u002Fintegration-database-and-service-testing\u002Fdatabase-fixtures-and-transactional-tests\u002F","database fixtures and transactional tests",".",[27,43,44,47,48,51,52,41],{},[14,45,46],{},"Faker >= 25"," if generated values are wanted; it ships with ",[14,49,50],{},"factory_boy"," as ",[14,53,54],{},"factory.Faker",[27,56,57,41],{},[14,58,59],{},"pytest >= 8.0",[19,61,63],{"id":62},"core-concept-defaults-carry-the-noise-arguments-carry-the-meaning","Core concept: defaults carry the noise, arguments carry the meaning",[10,65,66],{},"A factory is a function from \"what this test cares about\" to \"a valid object\". Everything not passed explicitly is noise the test does not depend on, and moving that noise out of the test body is the entire benefit.",[68,69,72,194],"figure",{"className":70},[71],"diagram",[73,74,81,82,81,86,81,90,81,98,81,108,81,118,81,124,81,131,81,135,81,139,81,143,81,150,81,154,81,158,81,162,81,168,81,172,81,177,81,180,81,185,81,189],"svg",{"viewBox":75,"role":76,"ariaLabelledBy":77,"xmlns":80},"0 0 840 270","img",[78,79],"fac-t","fac-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[83,84,85],"title",{"id":78},"Explicit construction versus a factory call",[87,88,89],"desc",{"id":79},"Two panels show the same test precondition. On the left, eleven explicit keyword arguments with one of them highlighted as the meaningful field. On the right, a single factory call with only that one field passed, and the remaining values supplied by defaults the reader never has to scan.",[91,92],"rect",{"x":93,"y":93,"width":94,"height":95,"rx":96,"fill":97},"0","840","270","14","#fffdf8",[99,100,107],"text",{"x":101,"y":102,"textAnchor":103,"fontSize":104,"fontWeight":105,"fill":106},"420","28","middle","16","700","#3d405b","The reader has to find the one field that matters",[91,109],{"x":110,"y":111,"width":112,"height":113,"rx":114,"fill":115,"stroke":116,"strokeWidth":117},"26","50","380","200","12","#fbe9e3","#e07a5f","1.8",[99,119,123],{"x":120,"y":121,"textAnchor":103,"fontSize":122,"fontWeight":105,"fill":106},"216","76","12.5","explicit construction",[91,125],{"x":126,"y":127,"width":128,"height":104,"rx":129,"fill":130},"46","88","340","4","rgba(61,64,91,0.14)",[91,132],{"x":126,"y":133,"width":134,"height":104,"rx":129,"fill":130},"110","300",[91,136],{"x":126,"y":137,"width":138,"height":104,"rx":129,"fill":130},"132","330",[91,140],{"x":126,"y":141,"width":142,"height":104,"rx":129,"fill":116},"154","250",[99,144,149],{"x":145,"y":146,"fontSize":147,"fontWeight":105,"fill":148},"306","167","11","#8f3d22","← the point",[91,151],{"x":126,"y":152,"width":153,"height":104,"rx":129,"fill":130},"176","320",[91,155],{"x":126,"y":156,"width":157,"height":104,"rx":129,"fill":130},"198","290",[91,159],{"x":126,"y":160,"width":161,"height":104,"rx":129,"fill":130},"220","315",[91,163],{"x":164,"y":111,"width":112,"height":113,"rx":114,"fill":165,"stroke":166,"strokeWidth":167},"434","#e6f0ea","#81b29a","2",[99,169,171],{"x":170,"y":121,"textAnchor":103,"fontSize":122,"fontWeight":105,"fill":106},"624","factory call",[91,173],{"x":174,"y":133,"width":157,"height":175,"rx":176,"fill":106},"454","20","5",[99,178,16],{"x":170,"y":179,"textAnchor":103,"fontSize":147,"fontWeight":105,"fill":97},"125",[99,181,184],{"x":170,"y":182,"textAnchor":103,"fontSize":183,"fill":106},"160","11.5","every other field has a default",[99,186,188],{"x":170,"y":187,"textAnchor":103,"fontSize":183,"fill":106},"182","that keeps the object valid",[99,190,193],{"x":170,"y":191,"textAnchor":103,"fontSize":183,"fontWeight":105,"fill":192},"212","#2a5f49","nothing to scan past",[195,196,197],"figcaption",{},"Both produce the same row. Only one of them survives being read six months later by somebody debugging a failure.",[19,199,201],{"id":200},"step-by-step-implementation","Step-by-step implementation",[203,204,206],"h3",{"id":205},"_1-one-factory-per-model-with-valid-defaults","1. One factory per model, with valid defaults",[208,209,214],"pre",{"className":210,"code":211,"language":212,"meta":213,"style":213},"language-python shiki shiki-themes github-light github-dark","import factory\n\nfrom myapp.models import Customer\n\n\nclass CustomerFactory(factory.alchemy.SQLAlchemyModelFactory):\n    class Meta:\n        model = Customer\n        sqlalchemy_session_persistence = \"flush\"   # never commit; see below\n\n    # Sequence, not Faker: uniqueness must be guaranteed, not probable.\n    email = factory.Sequence(lambda n: f\"customer-{n}@example.test\")\n    name = factory.Faker(\"name\")                   # variety with no assertion on it\n    country = \"GB\"\n    vat_exempt = False\n","python","",[14,215,216,224,231,237,242,247,253,259,265,271,276,282,288,294,300],{"__ignoreMap":213},[217,218,221],"span",{"class":219,"line":220},"line",1,[217,222,223],{},"import factory\n",[217,225,227],{"class":219,"line":226},2,[217,228,230],{"emptyLinePlaceholder":229},true,"\n",[217,232,234],{"class":219,"line":233},3,[217,235,236],{},"from myapp.models import Customer\n",[217,238,240],{"class":219,"line":239},4,[217,241,230],{"emptyLinePlaceholder":229},[217,243,245],{"class":219,"line":244},5,[217,246,230],{"emptyLinePlaceholder":229},[217,248,250],{"class":219,"line":249},6,[217,251,252],{},"class CustomerFactory(factory.alchemy.SQLAlchemyModelFactory):\n",[217,254,256],{"class":219,"line":255},7,[217,257,258],{},"    class Meta:\n",[217,260,262],{"class":219,"line":261},8,[217,263,264],{},"        model = Customer\n",[217,266,268],{"class":219,"line":267},9,[217,269,270],{},"        sqlalchemy_session_persistence = \"flush\"   # never commit; see below\n",[217,272,274],{"class":219,"line":273},10,[217,275,230],{"emptyLinePlaceholder":229},[217,277,279],{"class":219,"line":278},11,[217,280,281],{},"    # Sequence, not Faker: uniqueness must be guaranteed, not probable.\n",[217,283,285],{"class":219,"line":284},12,[217,286,287],{},"    email = factory.Sequence(lambda n: f\"customer-{n}@example.test\")\n",[217,289,291],{"class":219,"line":290},13,[217,292,293],{},"    name = factory.Faker(\"name\")                   # variety with no assertion on it\n",[217,295,297],{"class":219,"line":296},14,[217,298,299],{},"    country = \"GB\"\n",[217,301,303],{"class":219,"line":302},15,[217,304,305],{},"    vat_exempt = False\n",[10,307,308,311,312,315,316,319],{},[14,309,310],{},"sqlalchemy_session_persistence = \"flush\""," is the setting that makes factories compatible with a rolled-back test. ",[14,313,314],{},"flush"," sends the INSERT so primary keys are populated and subsequent queries see the row, without ending the transaction the fixture will roll back. ",[14,317,318],{},"commit"," would end it, and the test's isolation with it.",[203,321,323],{"id":322},"_2-bind-the-session-once","2. Bind the session once",[208,325,327],{"className":210,"code":326,"language":212,"meta":213,"style":213},"import pytest\n\n\n@pytest.fixture(autouse=True)\ndef bind_factories(db_session):\n    # Every factory writes through the test's transactional session.\n    for factory_class in (CustomerFactory, OrderFactory, OrderLineFactory):\n        factory_class._meta.sqlalchemy_session = db_session\n    yield\n",[14,328,329,334,338,342,347,352,357,362,367],{"__ignoreMap":213},[217,330,331],{"class":219,"line":220},[217,332,333],{},"import pytest\n",[217,335,336],{"class":219,"line":226},[217,337,230],{"emptyLinePlaceholder":229},[217,339,340],{"class":219,"line":233},[217,341,230],{"emptyLinePlaceholder":229},[217,343,344],{"class":219,"line":239},[217,345,346],{},"@pytest.fixture(autouse=True)\n",[217,348,349],{"class":219,"line":244},[217,350,351],{},"def bind_factories(db_session):\n",[217,353,354],{"class":219,"line":249},[217,355,356],{},"    # Every factory writes through the test's transactional session.\n",[217,358,359],{"class":219,"line":255},[217,360,361],{},"    for factory_class in (CustomerFactory, OrderFactory, OrderLineFactory):\n",[217,363,364],{"class":219,"line":261},[217,365,366],{},"        factory_class._meta.sqlalchemy_session = db_session\n",[217,368,369],{"class":219,"line":267},[217,370,371],{},"    yield\n",[10,373,374],{},"A factory that opens its own session is the single most common way rows escape a transactional fixture. Binding them in one autouse fixture makes the mistake impossible rather than merely discouraged.",[203,376,378],{"id":377},"_3-name-the-recurring-variations-as-traits","3. Name the recurring variations as traits",[208,380,382],{"className":210,"code":381,"language":212,"meta":213,"style":213},"import datetime as dt\n\nimport factory\n\n\nclass SubscriptionFactory(factory.alchemy.SQLAlchemyModelFactory):\n    class Meta:\n        model = Subscription\n        sqlalchemy_session_persistence = \"flush\"\n\n    status = \"active\"\n    expires_at = factory.LazyFunction(\n        lambda: dt.datetime(2027, 1, 1, tzinfo=dt.timezone.utc)\n    )\n\n    class Params:\n        # A trait is a named bundle of overrides — the vocabulary of the domain,\n        # rather than a repeated pair of keyword arguments in forty tests.\n        lapsed = factory.Trait(\n            status=\"expired\",\n            expires_at=dt.datetime(2020, 1, 1, tzinfo=dt.timezone.utc),\n        )\n",[14,383,384,389,393,397,401,405,410,414,419,424,428,433,438,443,448,452,458,464,470,476,482,488],{"__ignoreMap":213},[217,385,386],{"class":219,"line":220},[217,387,388],{},"import datetime as dt\n",[217,390,391],{"class":219,"line":226},[217,392,230],{"emptyLinePlaceholder":229},[217,394,395],{"class":219,"line":233},[217,396,223],{},[217,398,399],{"class":219,"line":239},[217,400,230],{"emptyLinePlaceholder":229},[217,402,403],{"class":219,"line":244},[217,404,230],{"emptyLinePlaceholder":229},[217,406,407],{"class":219,"line":249},[217,408,409],{},"class SubscriptionFactory(factory.alchemy.SQLAlchemyModelFactory):\n",[217,411,412],{"class":219,"line":255},[217,413,258],{},[217,415,416],{"class":219,"line":261},[217,417,418],{},"        model = Subscription\n",[217,420,421],{"class":219,"line":267},[217,422,423],{},"        sqlalchemy_session_persistence = \"flush\"\n",[217,425,426],{"class":219,"line":273},[217,427,230],{"emptyLinePlaceholder":229},[217,429,430],{"class":219,"line":278},[217,431,432],{},"    status = \"active\"\n",[217,434,435],{"class":219,"line":284},[217,436,437],{},"    expires_at = factory.LazyFunction(\n",[217,439,440],{"class":219,"line":290},[217,441,442],{},"        lambda: dt.datetime(2027, 1, 1, tzinfo=dt.timezone.utc)\n",[217,444,445],{"class":219,"line":296},[217,446,447],{},"    )\n",[217,449,450],{"class":219,"line":302},[217,451,230],{"emptyLinePlaceholder":229},[217,453,455],{"class":219,"line":454},16,[217,456,457],{},"    class Params:\n",[217,459,461],{"class":219,"line":460},17,[217,462,463],{},"        # A trait is a named bundle of overrides — the vocabulary of the domain,\n",[217,465,467],{"class":219,"line":466},18,[217,468,469],{},"        # rather than a repeated pair of keyword arguments in forty tests.\n",[217,471,473],{"class":219,"line":472},19,[217,474,475],{},"        lapsed = factory.Trait(\n",[217,477,479],{"class":219,"line":478},20,[217,480,481],{},"            status=\"expired\",\n",[217,483,485],{"class":219,"line":484},21,[217,486,487],{},"            expires_at=dt.datetime(2020, 1, 1, tzinfo=dt.timezone.utc),\n",[217,489,491],{"class":219,"line":490},22,[217,492,493],{},"        )\n",[208,495,497],{"className":210,"code":496,"language":212,"meta":213,"style":213},"def test_lapsed_subscription_blocks_access(db_session):\n    subscription = SubscriptionFactory(lapsed=True)\n    assert not subscription.grants_access()\n",[14,498,499,504,509],{"__ignoreMap":213},[217,500,501],{"class":219,"line":220},[217,502,503],{},"def test_lapsed_subscription_blocks_access(db_session):\n",[217,505,506],{"class":219,"line":226},[217,507,508],{},"    subscription = SubscriptionFactory(lapsed=True)\n",[217,510,511],{"class":219,"line":233},[217,512,513],{},"    assert not subscription.grants_access()\n",[10,515,516,517,520,521,524],{},"Traits are where factories stop being boilerplate reduction and start being documentation. ",[14,518,519],{},"lapsed=True"," is a term from the domain; ",[14,522,523],{},"status=\"expired\", expires_at=datetime(2020, 1, 1)"," is an implementation detail that will need updating in forty places when the model changes.",[203,526,528],{"id":527},"_4-keep-the-object-graph-shallow-by-default","4. Keep the object graph shallow by default",[208,530,532],{"className":210,"code":531,"language":212,"meta":213,"style":213},"import factory\n\n\nclass OrderFactory(factory.alchemy.SQLAlchemyModelFactory):\n    class Meta:\n        model = Order\n        sqlalchemy_session_persistence = \"flush\"\n\n    customer = factory.SubFactory(CustomerFactory)   # required for validity\n    placed_at = factory.LazyFunction(\n        lambda: dt.datetime(2026, 6, 1, tzinfo=dt.timezone.utc)\n    )\n\n    @factory.post_generation\n    def lines(self, create, extracted, **kwargs):\n        # Lines are OPT-IN: OrderFactory() makes one row, not four.\n        if not create or not extracted:\n            return\n        for amount in extracted:\n            OrderLineFactory(order=self, amount=amount)\n",[14,533,534,538,542,546,551,555,560,564,568,573,578,583,587,591,596,601,606,611,616,621],{"__ignoreMap":213},[217,535,536],{"class":219,"line":220},[217,537,223],{},[217,539,540],{"class":219,"line":226},[217,541,230],{"emptyLinePlaceholder":229},[217,543,544],{"class":219,"line":233},[217,545,230],{"emptyLinePlaceholder":229},[217,547,548],{"class":219,"line":239},[217,549,550],{},"class OrderFactory(factory.alchemy.SQLAlchemyModelFactory):\n",[217,552,553],{"class":219,"line":244},[217,554,258],{},[217,556,557],{"class":219,"line":249},[217,558,559],{},"        model = Order\n",[217,561,562],{"class":219,"line":255},[217,563,423],{},[217,565,566],{"class":219,"line":261},[217,567,230],{"emptyLinePlaceholder":229},[217,569,570],{"class":219,"line":267},[217,571,572],{},"    customer = factory.SubFactory(CustomerFactory)   # required for validity\n",[217,574,575],{"class":219,"line":273},[217,576,577],{},"    placed_at = factory.LazyFunction(\n",[217,579,580],{"class":219,"line":278},[217,581,582],{},"        lambda: dt.datetime(2026, 6, 1, tzinfo=dt.timezone.utc)\n",[217,584,585],{"class":219,"line":284},[217,586,447],{},[217,588,589],{"class":219,"line":290},[217,590,230],{"emptyLinePlaceholder":229},[217,592,593],{"class":219,"line":296},[217,594,595],{},"    @factory.post_generation\n",[217,597,598],{"class":219,"line":302},[217,599,600],{},"    def lines(self, create, extracted, **kwargs):\n",[217,602,603],{"class":219,"line":454},[217,604,605],{},"        # Lines are OPT-IN: OrderFactory() makes one row, not four.\n",[217,607,608],{"class":219,"line":460},[217,609,610],{},"        if not create or not extracted:\n",[217,612,613],{"class":219,"line":466},[217,614,615],{},"            return\n",[217,617,618],{"class":219,"line":472},[217,619,620],{},"        for amount in extracted:\n",[217,622,623],{"class":219,"line":478},[217,624,625],{},"            OrderLineFactory(order=self, amount=amount)\n",[208,627,629],{"className":210,"code":628,"language":212,"meta":213,"style":213},"def test_total_sums_line_amounts(db_session):\n    order = OrderFactory(lines=[100, 250, 75])       # exactly the rows this test needs\n    assert order.total() == 425\n",[14,630,631,636,641],{"__ignoreMap":213},[217,632,633],{"class":219,"line":220},[217,634,635],{},"def test_total_sums_line_amounts(db_session):\n",[217,637,638],{"class":219,"line":226},[217,639,640],{},"    order = OrderFactory(lines=[100, 250, 75])       # exactly the rows this test needs\n",[217,642,643],{"class":219,"line":233},[217,644,645],{},"    assert order.total() == 425\n",[10,647,648,649,652,653,655,656,659],{},"A ",[14,650,651],{},"SubFactory"," on every relation is how a factory that looks innocent inserts fifteen rows. Required relations get a ",[14,654,651],{},"; optional ones get a ",[14,657,658],{},"post_generation"," hook that does nothing unless asked.",[203,661,663],{"id":662},"_5-seed-the-randomness","5. Seed the randomness",[208,665,667],{"className":210,"code":666,"language":212,"meta":213,"style":213},"import pytest\nfrom factory.random import reseed_random\n\n\n@pytest.fixture(autouse=True, scope=\"session\")\ndef deterministic_factories(request):\n    seed = request.config.getoption(\"--factory-seed\", default=None) or 20260918\n    reseed_random(seed)          # factory_boy's Faker and fuzzy attributes\n    print(f\"\\nfactory seed: {seed}\")\n",[14,668,669,673,678,682,686,691,696,701,706],{"__ignoreMap":213},[217,670,671],{"class":219,"line":220},[217,672,333],{},[217,674,675],{"class":219,"line":226},[217,676,677],{},"from factory.random import reseed_random\n",[217,679,680],{"class":219,"line":233},[217,681,230],{"emptyLinePlaceholder":229},[217,683,684],{"class":219,"line":239},[217,685,230],{"emptyLinePlaceholder":229},[217,687,688],{"class":219,"line":244},[217,689,690],{},"@pytest.fixture(autouse=True, scope=\"session\")\n",[217,692,693],{"class":219,"line":249},[217,694,695],{},"def deterministic_factories(request):\n",[217,697,698],{"class":219,"line":255},[217,699,700],{},"    seed = request.config.getoption(\"--factory-seed\", default=None) or 20260918\n",[217,702,703],{"class":219,"line":261},[217,704,705],{},"    reseed_random(seed)          # factory_boy's Faker and fuzzy attributes\n",[217,707,708],{"class":219,"line":267},[217,709,710],{},"    print(f\"\\nfactory seed: {seed}\")\n",[10,712,713,714,717],{},"Printing the seed is the point. A failure caused by a generated value is reproducible with ",[14,715,716],{},"--factory-seed=\u003Cprinted value>",", and irreproducible without it.",[19,719,721],{"id":720},"verification","Verification",[10,723,724],{},"Factories are correct when two consecutive runs produce identical data and no rows survive a test. Both are checkable directly:",[208,726,728],{"className":210,"code":727,"language":212,"meta":213,"style":213},"def test_factories_are_deterministic(db_session):\n    first = CustomerFactory()\n    assert first.email == \"customer-0@example.test\"      # sequence starts at 0 per run\n\n\ndef test_factory_rows_do_not_leak(db_session):\n    # Run after any test that created customers; the rollback must have undone it.\n    assert db_session.query(Customer).count() == 0\n",[14,729,730,735,740,745,749,753,758,763],{"__ignoreMap":213},[217,731,732],{"class":219,"line":220},[217,733,734],{},"def test_factories_are_deterministic(db_session):\n",[217,736,737],{"class":219,"line":226},[217,738,739],{},"    first = CustomerFactory()\n",[217,741,742],{"class":219,"line":233},[217,743,744],{},"    assert first.email == \"customer-0@example.test\"      # sequence starts at 0 per run\n",[217,746,747],{"class":219,"line":239},[217,748,230],{"emptyLinePlaceholder":229},[217,750,751],{"class":219,"line":244},[217,752,230],{"emptyLinePlaceholder":229},[217,754,755],{"class":219,"line":249},[217,756,757],{},"def test_factory_rows_do_not_leak(db_session):\n",[217,759,760],{"class":219,"line":255},[217,761,762],{},"    # Run after any test that created customers; the rollback must have undone it.\n",[217,764,765],{"class":219,"line":261},[217,766,767],{},"    assert db_session.query(Customer).count() == 0\n",[208,769,773],{"className":770,"code":771,"language":772,"meta":213,"style":213},"language-bash shiki shiki-themes github-light github-dark","pytest tests\u002F -p no:randomly -q && pytest tests\u002F -p no:randomly -q   # identical output\n","bash",[14,774,775],{"__ignoreMap":213},[217,776,777,781,785,789,792,795,799,801,803,805,807,809],{"class":219,"line":220},[217,778,780],{"class":779},"sScJk","pytest",[217,782,784],{"class":783},"sZZnC"," tests\u002F",[217,786,788],{"class":787},"sj4cs"," -p",[217,790,791],{"class":783}," no:randomly",[217,793,794],{"class":787}," -q",[217,796,798],{"class":797},"sVt8B"," && ",[217,800,780],{"class":779},[217,802,784],{"class":783},[217,804,788],{"class":787},[217,806,791],{"class":783},[217,808,794],{"class":787},[217,810,812],{"class":811},"sJ8bj","   # identical output\n",[10,814,815],{},"Two runs with identical output is the standard to hold. A suite where the second run differs has non-determinism somewhere, and generated data is the usual source.",[19,817,819],{"id":818},"troubleshooting","Troubleshooting",[821,822,823,839],"table",{},[824,825,826],"thead",{},[827,828,829,833,836],"tr",{},[830,831,832],"th",{},"Symptom",[830,834,835],{},"Root cause",[830,837,838],{},"Fix",[840,841,842,862,877,893,904,918],"tbody",{},[827,843,844,850,856],{},[845,846,847],"td",{},[14,848,849],{},"IntegrityError: duplicate key",[845,851,852,855],{},[14,853,854],{},"Faker"," used for a unique column",[845,857,858,859],{},"Use ",[14,860,861],{},"factory.Sequence",[827,863,864,867,872],{},[845,865,866],{},"Rows survive between tests",[845,868,869,870],{},"Factory bound to its own session, or persistence set to ",[14,871,318],{},[845,873,874,875],{},"Bind the test session; use ",[14,876,314],{},[827,878,879,882,887],{},[845,880,881],{},"One test creates dozens of rows",[845,883,884,886],{},[14,885,651],{}," chains on optional relations",[845,888,889,890,892],{},"Convert to ",[14,891,658],{}," opt-ins",[827,894,895,898,901],{},[845,896,897],{},"Test fails one run in a hundred",[845,899,900],{},"Unseeded random data hitting a boundary",[845,902,903],{},"Seed the generator and print the seed",[827,905,906,912,915],{},[845,907,908,911],{},[14,909,910],{},"AttributeError"," on a factory attribute",[845,913,914],{},"Model field renamed, factory not updated",[845,916,917],{},"Keep factories beside the models in review",[827,919,920,923,926],{},[845,921,922],{},"Identifiers differ between runs",[845,924,925],{},"Sequences shared across an xdist session",[845,927,928],{},"Prefix sequences with the worker id",[19,930,932],{"id":931},"builders-when-a-factory-is-the-wrong-shape","Builders when a factory is the wrong shape",[10,934,935,937],{},[14,936,50],{}," is optimised for persisted models. For value objects, API payloads or configuration, a plain builder is often clearer and has no dependency at all:",[208,939,941],{"className":210,"code":940,"language":212,"meta":213,"style":213},"from dataclasses import dataclass, replace\n\n\n@dataclass(frozen=True)\nclass OrderRequest:\n    customer_id: str = \"cus_test\"\n    currency: str = \"GBP\"\n    amount: int = 1000\n    idempotency_key: str = \"key-1\"\n\n\ndef an_order(**overrides) -> OrderRequest:\n    \"\"\"A builder: defaults plus the fields this test is about.\"\"\"\n    return replace(OrderRequest(), **overrides)\n",[14,942,943,948,952,956,961,966,971,976,981,986,990,994,999,1004],{"__ignoreMap":213},[217,944,945],{"class":219,"line":220},[217,946,947],{},"from dataclasses import dataclass, replace\n",[217,949,950],{"class":219,"line":226},[217,951,230],{"emptyLinePlaceholder":229},[217,953,954],{"class":219,"line":233},[217,955,230],{"emptyLinePlaceholder":229},[217,957,958],{"class":219,"line":239},[217,959,960],{},"@dataclass(frozen=True)\n",[217,962,963],{"class":219,"line":244},[217,964,965],{},"class OrderRequest:\n",[217,967,968],{"class":219,"line":249},[217,969,970],{},"    customer_id: str = \"cus_test\"\n",[217,972,973],{"class":219,"line":255},[217,974,975],{},"    currency: str = \"GBP\"\n",[217,977,978],{"class":219,"line":261},[217,979,980],{},"    amount: int = 1000\n",[217,982,983],{"class":219,"line":267},[217,984,985],{},"    idempotency_key: str = \"key-1\"\n",[217,987,988],{"class":219,"line":273},[217,989,230],{"emptyLinePlaceholder":229},[217,991,992],{"class":219,"line":278},[217,993,230],{"emptyLinePlaceholder":229},[217,995,996],{"class":219,"line":284},[217,997,998],{},"def an_order(**overrides) -> OrderRequest:\n",[217,1000,1001],{"class":219,"line":290},[217,1002,1003],{},"    \"\"\"A builder: defaults plus the fields this test is about.\"\"\"\n",[217,1005,1006],{"class":219,"line":296},[217,1007,1008],{},"    return replace(OrderRequest(), **overrides)\n",[208,1010,1012],{"className":210,"code":1011,"language":212,"meta":213,"style":213},"def test_zero_amount_is_rejected(api):\n    response = api.post(\"\u002Forders\", json=an_order(amount=0).__dict__)\n    assert response.status_code == 422\n",[14,1013,1014,1019,1024],{"__ignoreMap":213},[217,1015,1016],{"class":219,"line":220},[217,1017,1018],{},"def test_zero_amount_is_rejected(api):\n",[217,1020,1021],{"class":219,"line":226},[217,1022,1023],{},"    response = api.post(\"\u002Forders\", json=an_order(amount=0).__dict__)\n",[217,1025,1026],{"class":219,"line":233},[217,1027,1028],{},"    assert response.status_code == 422\n",[10,1030,1031,1034,1035,1038],{},[14,1032,1033],{},"dataclasses.replace"," gives immutable overrides in one line, which covers most payload-building needs. The pattern scales down better than a factory class and, because the defaults are a frozen dataclass, a typo in an override is a ",[14,1036,1037],{},"TypeError"," at the call site rather than a silently ignored keyword.",[10,1040,1041,1042,1044],{},"The choice between the two is about persistence. Anything that must be inserted, related and flushed benefits from ",[14,1043,50],{},"'s session handling; anything that is just a value is better as a function.",[19,1046,1048],{"id":1047},"where-factories-go-wrong-at-scale","Where factories go wrong at scale",[10,1050,1051],{},"Three failure modes account for most of the pain teams report after a year of factory use.",[10,1053,1054,1058,1059,1062,1063,1066,1067,1066,1070,1066,1073,1066,1076,1079],{},[1055,1056,1057],"strong",{},"The god factory."," One ",[14,1060,1061],{},"UserFactory"," grows parameters for every scenario in the suite — ",[14,1064,1065],{},"with_subscription",", ",[14,1068,1069],{},"with_orders",[14,1071,1072],{},"verified",[14,1074,1075],{},"admin",[14,1077,1078],{},"suspended"," — until it is a small program nobody can change safely. Traits help, but the real remedy is composition: separate factories that a scenario fixture assembles, so no single factory knows about every test.",[10,1081,1082,1085,1086,1089],{},[1055,1083,1084],{},"Assertions on generated values."," ",[14,1087,1088],{},"assert customer.name == \"John Smith\""," passes until Faker's word list changes. The rule is that a value produced by the factory is never asserted on directly; the test asserts on values it supplied or on relationships it established.",[10,1091,1092,1095],{},[1055,1093,1094],{},"Factories reaching into the application."," A factory that calls a service method to create its object is running application logic during setup, so an unrelated change to that method breaks hundreds of tests at once. Factories write to the model layer; they do not invoke use cases.",[68,1097,1099,1203],{"className":1098},[71],[73,1100,81,1105,81,1108,81,1111,81,1115,81,1119,81,1124,81,1127,81,1132,81,1137,81,1141,81,1145,81,1148,81,1152,81,1156,81,1158,81,1161,81,1165,81,1168,81,1172,81,1175,81,1178,81,1181,81,1183,81,1187,81,1191,81,1194,81,1197,81,1200],{"viewBox":1101,"role":76,"ariaLabelledBy":1102,"xmlns":80},"0 0 820 256",[1103,1104],"scale-t","scale-d",[83,1106,1107],{"id":1103},"Three ways factories decay, and the correction for each",[87,1109,1110],{"id":1104},"Three cards. The god factory accumulates parameters for every scenario and is corrected by composing small factories in scenario fixtures. Assertions on generated values break when the generator changes and are corrected by asserting only on supplied values. Factories that call application services couple setup to product code and are corrected by writing directly to the model layer.",[91,1112],{"x":93,"y":93,"width":1113,"height":1114,"rx":96,"fill":97},"820","256",[99,1116,1118],{"x":1117,"y":102,"textAnchor":103,"fontSize":104,"fontWeight":105,"fill":106},"410","How factories decay after a year",[91,1120],{"x":1121,"y":111,"width":1122,"height":1123,"rx":114,"fill":97,"stroke":116,"strokeWidth":167},"24","248","186",[91,1125],{"x":1121,"y":111,"width":1122,"height":1126,"rx":114,"fill":106},"32",[99,1128,1131],{"x":1129,"y":1130,"textAnchor":103,"fontSize":114,"fontWeight":105,"fill":97},"148","71","the god factory",[99,1133,1136],{"x":1134,"y":1135,"fontSize":147,"fill":106},"40","106","one class, thirty params",[99,1138,1140],{"x":1134,"y":1139,"fontSize":147,"fill":106},"128","every scenario bolted on",[99,1142,1144],{"x":1134,"y":1143,"fontSize":147,"fill":148},"150","nobody dares change it",[99,1146,1147],{"x":1134,"y":187,"fontSize":147,"fontWeight":105,"fill":192},"fix: compose small ones",[99,1149,1151],{"x":1134,"y":1150,"fontSize":147,"fill":106},"204","in scenario fixtures",[91,1153],{"x":1154,"y":111,"width":1122,"height":1123,"rx":114,"fill":97,"stroke":1155,"strokeWidth":167},"286","#f2cc8f",[91,1157],{"x":1154,"y":111,"width":1122,"height":1126,"rx":114,"fill":106},[99,1159,1160],{"x":1117,"y":1130,"textAnchor":103,"fontSize":114,"fontWeight":105,"fill":97},"asserting on defaults",[99,1162,1164],{"x":1163,"y":1135,"fontSize":147,"fill":106},"302","assert name == \"John\"",[99,1166,1167],{"x":1163,"y":1139,"fontSize":147,"fill":106},"passes until the word",[99,1169,1171],{"x":1163,"y":1143,"fontSize":147,"fill":1170},"#8a5a00","list is updated upstream",[99,1173,1174],{"x":1163,"y":187,"fontSize":147,"fontWeight":105,"fill":192},"fix: assert only on",[99,1176,1177],{"x":1163,"y":1150,"fontSize":147,"fill":106},"values you supplied",[91,1179],{"x":1180,"y":111,"width":1122,"height":1123,"rx":114,"fill":97,"stroke":166,"strokeWidth":167},"548",[91,1182],{"x":1180,"y":111,"width":1122,"height":1126,"rx":114,"fill":106},[99,1184,1186],{"x":1185,"y":1130,"textAnchor":103,"fontSize":114,"fontWeight":105,"fill":97},"672","calling the app",[99,1188,1190],{"x":1189,"y":1135,"fontSize":147,"fill":106},"564","factory invokes a service",[99,1192,1193],{"x":1189,"y":1139,"fontSize":147,"fill":106},"to build its object",[99,1195,1196],{"x":1189,"y":1143,"fontSize":147,"fill":148},"one change breaks 300 tests",[99,1198,1199],{"x":1189,"y":187,"fontSize":147,"fontWeight":105,"fill":192},"fix: write to the model",[99,1201,1202],{"x":1189,"y":1150,"fontSize":147,"fill":106},"layer directly",[195,1204,1205],{},"All three decay modes share a cause: the factory acquired responsibilities that belong to the test or to the application.",[19,1207,1209],{"id":1208},"factories-under-parallel-execution","Factories under parallel execution",[10,1211,1212,1213,1216,1217,1220,1221,1224,1225,1228],{},"Sequences are per-process, so two ",[14,1214,1215],{},"pytest-xdist"," workers both start at zero and both try to insert ",[14,1218,1219],{},"customer-0@example.test",". With per-worker databases the collision is invisible; with a shared database it is an ",[14,1222,1223],{},"IntegrityError"," that appears only under ",[14,1226,1227],{},"-n auto"," and looks like a race in the application.",[208,1230,1232],{"className":210,"code":1231,"language":212,"meta":213,"style":213},"import os\n\nimport factory\n\nWORKER = os.environ.get(\"PYTEST_XDIST_WORKER\", \"gw0\")\n\n\nclass CustomerFactory(factory.alchemy.SQLAlchemyModelFactory):\n    class Meta:\n        model = Customer\n\n    # Worker-prefixed: unique across processes, still deterministic within one.\n    email = factory.Sequence(lambda n: f\"customer-{WORKER}-{n}@example.test\")\n",[14,1233,1234,1239,1243,1247,1251,1256,1260,1264,1268,1272,1276,1280,1285],{"__ignoreMap":213},[217,1235,1236],{"class":219,"line":220},[217,1237,1238],{},"import os\n",[217,1240,1241],{"class":219,"line":226},[217,1242,230],{"emptyLinePlaceholder":229},[217,1244,1245],{"class":219,"line":233},[217,1246,223],{},[217,1248,1249],{"class":219,"line":239},[217,1250,230],{"emptyLinePlaceholder":229},[217,1252,1253],{"class":219,"line":244},[217,1254,1255],{},"WORKER = os.environ.get(\"PYTEST_XDIST_WORKER\", \"gw0\")\n",[217,1257,1258],{"class":219,"line":249},[217,1259,230],{"emptyLinePlaceholder":229},[217,1261,1262],{"class":219,"line":255},[217,1263,230],{"emptyLinePlaceholder":229},[217,1265,1266],{"class":219,"line":261},[217,1267,252],{},[217,1269,1270],{"class":219,"line":267},[217,1271,258],{},[217,1273,1274],{"class":219,"line":273},[217,1275,264],{},[217,1277,1278],{"class":219,"line":278},[217,1279,230],{"emptyLinePlaceholder":229},[217,1281,1282],{"class":219,"line":284},[217,1283,1284],{},"    # Worker-prefixed: unique across processes, still deterministic within one.\n",[217,1286,1287],{"class":219,"line":290},[217,1288,1289],{},"    email = factory.Sequence(lambda n: f\"customer-{WORKER}-{n}@example.test\")\n",[10,1291,1292],{},"The prefix keeps the value deterministic per worker, so a failure is still reproducible by running that worker's tests alone. An alternative — using a UUID — removes the collision and the determinism together, which trades a rare crash for permanently unreadable test data.",[10,1294,1295,1296,1299],{},"Two further details matter under parallelism. Session-scoped factory state, such as a cached \"current tenant\", is created once per worker rather than once per run, so any assumption about there being exactly one of something is wrong. And the ",[14,1297,1298],{},"--factory-seed"," above should incorporate the worker id, or every worker generates the same \"random\" names and any uniqueness constraint on a Faker-generated column fails immediately.",[19,1301,1303],{"id":1302},"seeding-a-realistic-baseline-without-coupling-tests-to-it","Seeding a realistic baseline without coupling tests to it",[10,1305,1306],{},"Some suites need more than the rows a single test creates: a catalogue of products, a tax table, a set of feature flags. The temptation is a large seed fixture that every test inherits, and it is a trap — within a year tests depend on rows they never mention, and nobody can change the seed without breaking things at random.",[10,1308,1309,1310,1314,1315,1318],{},"The workable split is between ",[1311,1312,1313],"em",{},"reference data",", which is immutable and genuinely global, and ",[1311,1316,1317],{},"scenario data",", which belongs to the test that needs it.",[208,1320,1322],{"className":210,"code":1321,"language":212,"meta":213,"style":213},"import pytest\n\n\n@pytest.fixture(scope=\"session\", autouse=True)\ndef reference_data(engine):\n    \"\"\"Rows the domain cannot function without: currencies, countries, tax bands.\n    Committed once, outside any test transaction, and never modified.\"\"\"\n    with Session(engine) as session:\n        session.add_all([\n            Currency(code=\"GBP\", minor_units=2),\n            Currency(code=\"JPY\", minor_units=0),   # a deliberate edge case\n            TaxBand(country=\"GB\", rate=Decimal(\"0.20\")),\n        ])\n        session.commit()\n",[14,1323,1324,1328,1332,1336,1341,1346,1351,1356,1361,1366,1371,1376,1381,1386],{"__ignoreMap":213},[217,1325,1326],{"class":219,"line":220},[217,1327,333],{},[217,1329,1330],{"class":219,"line":226},[217,1331,230],{"emptyLinePlaceholder":229},[217,1333,1334],{"class":219,"line":233},[217,1335,230],{"emptyLinePlaceholder":229},[217,1337,1338],{"class":219,"line":239},[217,1339,1340],{},"@pytest.fixture(scope=\"session\", autouse=True)\n",[217,1342,1343],{"class":219,"line":244},[217,1344,1345],{},"def reference_data(engine):\n",[217,1347,1348],{"class":219,"line":249},[217,1349,1350],{},"    \"\"\"Rows the domain cannot function without: currencies, countries, tax bands.\n",[217,1352,1353],{"class":219,"line":255},[217,1354,1355],{},"    Committed once, outside any test transaction, and never modified.\"\"\"\n",[217,1357,1358],{"class":219,"line":261},[217,1359,1360],{},"    with Session(engine) as session:\n",[217,1362,1363],{"class":219,"line":267},[217,1364,1365],{},"        session.add_all([\n",[217,1367,1368],{"class":219,"line":273},[217,1369,1370],{},"            Currency(code=\"GBP\", minor_units=2),\n",[217,1372,1373],{"class":219,"line":278},[217,1374,1375],{},"            Currency(code=\"JPY\", minor_units=0),   # a deliberate edge case\n",[217,1377,1378],{"class":219,"line":284},[217,1379,1380],{},"            TaxBand(country=\"GB\", rate=Decimal(\"0.20\")),\n",[217,1382,1383],{"class":219,"line":290},[217,1384,1385],{},"        ])\n",[217,1387,1388],{"class":219,"line":296},[217,1389,1390],{},"        session.commit()\n",[10,1392,1393,1394,1397],{},"Two rules keep this safe. Reference data is small enough to read in one screen — if it is not, some of it is scenario data in disguise. And no test may modify it: a test that needs a different tax rate creates its own ",[14,1395,1396],{},"TaxBand"," row rather than mutating the shared one, because mutation of committed reference data survives the rollback and poisons everything after it.",[10,1399,1400],{},"The edge case in that fixture is deliberate. Including a zero-decimal currency alongside the ordinary one means every test that touches money has a chance of exercising the rounding path, and the cost is one extra row. Reference data chosen this way does real work; reference data that is just \"the happy-path values\" only adds setup.",[10,1402,1403],{},"Where a suite genuinely needs a large, realistic dataset — search relevance, reporting, migration performance — load it from a fixture file into a dedicated database, mark those tests, and keep them out of the default run. A hundred thousand rows restored once per session is fine; a hundred thousand rows every test is a different kind of suite.",[68,1405,1407,1491],{"className":1406},[71],[73,1408,81,1413,81,1416,81,1419,81,1421,81,1424,81,1429,81,1433,81,1437,81,1441,81,1447,81,1451,81,1454,81,1458,81,1462,81,1465,81,1468,81,1471,81,1474,81,1476,81,1479,81,1482,81,1485,81,1488],{"viewBox":1409,"role":76,"ariaLabelledBy":1410,"xmlns":80},"0 0 820 250",[1411,1412],"seed-t","seed-d",[83,1414,1415],{"id":1411},"Reference data outside the transaction, scenario data inside it",[87,1417,1418],{"id":1412},"A session boundary contains a committed reference-data layer of currencies and tax bands that survives every test. Inside it, each test opens a transaction in which factories create scenario rows, and the transaction is rolled back at the end so only the reference layer remains.",[91,1420],{"x":93,"y":93,"width":1113,"height":142,"rx":96,"fill":97},[99,1422,1423],{"x":1117,"y":102,"textAnchor":103,"fontSize":104,"fontWeight":105,"fill":106},"Two layers with different lifetimes",[91,1425],{"x":110,"y":1426,"width":1427,"height":1428,"rx":114,"fill":165,"stroke":166,"strokeWidth":167},"48","768","80",[99,1430,1432],{"x":126,"y":1431,"fontSize":122,"fontWeight":105,"fill":106},"72","reference data — committed once per session",[99,1434,1436],{"x":126,"y":1435,"fontSize":147,"fill":106},"94","currencies · countries · tax bands · feature flags",[99,1438,1440],{"x":126,"y":1439,"fontSize":147,"fill":192},"114","immutable by convention; survives every rollback",[91,1442],{"x":110,"y":1443,"width":1444,"height":1445,"rx":147,"fill":1446,"stroke":1155,"strokeWidth":117},"140","244","92","#f7f0da",[99,1448,1450],{"x":1129,"y":1449,"textAnchor":103,"fontSize":114,"fontWeight":105,"fill":106},"164","test 1",[99,1452,1453],{"x":1129,"y":1123,"textAnchor":103,"fontSize":147,"fill":106},"CustomerFactory()",[99,1455,1457],{"x":1129,"y":1456,"textAnchor":103,"fontSize":147,"fill":106},"206","OrderFactory(lines=[100])",[99,1459,1461],{"x":1129,"y":1460,"textAnchor":103,"fontSize":147,"fill":1170},"224","rolled back",[91,1463],{"x":1464,"y":1443,"width":1444,"height":1445,"rx":147,"fill":1446,"stroke":1155,"strokeWidth":117},"288",[99,1466,1467],{"x":1117,"y":1449,"textAnchor":103,"fontSize":114,"fontWeight":105,"fill":106},"test 2",[99,1469,1470],{"x":1117,"y":1123,"textAnchor":103,"fontSize":147,"fill":106},"SubscriptionFactory(lapsed=True)",[99,1472,1473],{"x":1117,"y":1456,"textAnchor":103,"fontSize":147,"fill":106},"sees the same currencies",[99,1475,1461],{"x":1117,"y":1460,"textAnchor":103,"fontSize":147,"fill":1170},[91,1477],{"x":1478,"y":1443,"width":1444,"height":1445,"rx":147,"fill":1446,"stroke":1155,"strokeWidth":117},"550",[99,1480,1481],{"x":1185,"y":1449,"textAnchor":103,"fontSize":114,"fontWeight":105,"fill":106},"test 3",[99,1483,1484],{"x":1185,"y":1123,"textAnchor":103,"fontSize":147,"fill":106},"an_order(amount=0)",[99,1486,1487],{"x":1185,"y":1456,"textAnchor":103,"fontSize":147,"fill":106},"no rows at all",[99,1489,1490],{"x":1185,"y":1460,"textAnchor":103,"fontSize":147,"fill":1170},"nothing to undo",[195,1492,1493],{},"The upper band is committed and shared; everything in the lower bands disappears. A test that mutates the upper band breaks that arrangement silently.",[10,1495,1496,1497,1500],{},"One consequence of this split is worth stating explicitly, because it comes up in review repeatedly: a test that needs a ",[1311,1498,1499],{},"variant"," of reference data — a currency with three decimal places, a tax band that has not come into effect yet — creates its own row rather than editing the shared one. The extra row costs nothing, rolls back with everything else, and makes the test self-contained enough to read without knowing what the session fixture committed.",[19,1502,1504],{"id":1503},"reviewing-factories-like-production-code","Reviewing factories like production code",[10,1506,1507],{},"Factories accumulate quietly, because a change to one is rarely the point of the change it appears in. A few review habits keep them from drifting.",[10,1509,1510,1513,1514,1517],{},[1055,1511,1512],{},"A new field needs a default in the factory, in the same change."," Otherwise the next hundred tests to run fail on a ",[14,1515,1516],{},"NOT NULL"," violation and somebody adds the field to fifty call sites instead of one factory.",[10,1519,1520,1085,1523,1526,1527,1530],{},[1055,1521,1522],{},"A new trait must be named for the domain, not the implementation.",[14,1524,1525],{},"lapsed"," survives a schema change; ",[14,1528,1529],{},"status_expired_and_date_in_past"," does not.",[10,1532,1533,1538],{},[1055,1534,648,1535,1537],{},[14,1536,651],{}," added to an existing factory is a performance change."," It multiplies the rows every test in the suite inserts, and the reviewer should ask whether the relation is required for validity or merely convenient for one test.",[10,1540,1541,1544,1545,1548],{},[1055,1542,1543],{},"Assertions on factory-supplied values are a bug in the test."," When review catches ",[14,1546,1547],{},"assert order.currency == \"GBP\""," where nothing passed a currency, the fix is to pass it explicitly — the test evidently does depend on it.",[208,1550,1552],{"className":770,"code":1551,"language":772,"meta":213,"style":213},"# A cheap audit: which factories are used most, and which are never used at all?\ngrep -rho \"[A-Za-z]*Factory(\" tests\u002F | sort | uniq -c | sort -rn | head -20\n",[14,1553,1554,1559],{"__ignoreMap":213},[217,1555,1556],{"class":219,"line":220},[217,1557,1558],{"class":811},"# A cheap audit: which factories are used most, and which are never used at all?\n",[217,1560,1561,1564,1567,1570,1572,1576,1579,1581,1584,1587,1589,1591,1594,1596,1599],{"class":219,"line":226},[217,1562,1563],{"class":779},"grep",[217,1565,1566],{"class":787}," -rho",[217,1568,1569],{"class":783}," \"[A-Za-z]*Factory(\"",[217,1571,784],{"class":783},[217,1573,1575],{"class":1574},"szBVR"," |",[217,1577,1578],{"class":779}," sort",[217,1580,1575],{"class":1574},[217,1582,1583],{"class":779}," uniq",[217,1585,1586],{"class":787}," -c",[217,1588,1575],{"class":1574},[217,1590,1578],{"class":779},[217,1592,1593],{"class":787}," -rn",[217,1595,1575],{"class":1574},[217,1597,1598],{"class":779}," head",[217,1600,1601],{"class":787}," -20\n",[10,1603,1604],{},"The unused ones are worth deleting; the heavily used ones are worth reading carefully, because a default in a factory used by four hundred tests is effectively a global constant of the suite. Treating that number as a signal — anything above a hundred uses gets the same scrutiny as a public function signature — is what stops the god factory from forming in the first place. A useful supplement is to run the audit again after any large refactor: a factory whose usage count halves overnight usually means somebody copied it rather than extended it, and two near-identical factories drift apart far faster than one factory with a trait.",[10,1606,1607],{},"A last habit is to delete aggressively. Factories for models that no longer exist, traits whose scenario was removed, builders left behind by a refactor: all of them still get read by whoever is trying to understand the suite, and all of them still have to be updated when a shared base class changes. The audit command above finds the unused ones in a second, and removing them costs nothing.",[10,1609,1610],{},"Finally, keep factories in the same review as the model they build. A reviewer looking at a migration that adds a column and at the factory that now supplies it can see in one place whether the default is sensible; the same two changes split across two pull requests produce a week of red builds in between.",[19,1612,1614],{"id":1613},"frequently-asked-questions","Frequently Asked Questions",[10,1616,1617,1620],{},[1055,1618,1619],{},"Should factories use random data or fixed defaults?","\nFixed or sequential defaults for anything the test might assert on, random only where variety genuinely helps and the value is never asserted. Random names and addresses are fine; random amounts, dates and identifiers produce tests that fail once a month with no way to reproduce. Where randomness is wanted deliberately, seed it and print the seed.",[10,1622,1623,1626],{},[1055,1624,1625],{},"What is the difference between a factory and a fixture?","\nA fixture is a value pytest provides before the test runs; a factory is a callable the test invokes with its own arguments. Fixtures are right for shared infrastructure such as a session or a client, factories for the rows a specific test needs, because only the test knows which fields matter to it.",[10,1628,1629,1632,1633,1635,1636,1638],{},[1055,1630,1631],{},"How do I avoid factories that create half the database?","\nMake sub-objects lazy. ",[14,1634,50],{},"'s ",[14,1637,651],{}," creates a related object on every call, so a three-level chain inserts a dozen rows per test. Use a trait or an explicit argument to opt into the deeper graph, and let the default build only what the object requires to be valid.",[10,1640,1641,1644],{},[1055,1642,1643],{},"Do factories belong in the package or in the test directory?","\nIn the test directory by default, because they encode test conventions rather than product behaviour. Publish them as a package extra only when downstream consumers — another service's test suite, a plugin — genuinely need to build your models, and treat their signatures as public API from that point.",[10,1646,1647,1650,1651,1653,1654,1656],{},[1055,1648,1649],{},"How do factories interact with a transactional test fixture?","\nThey must use the same session and must flush rather than commit. A factory that opens its own session writes outside the test's transaction and leaks rows; one that calls ",[14,1652,318],{}," ends the savepoint. Bind the factory's session in a fixture and use ",[14,1655,314],{}," to populate identifiers.",[19,1658,1660],{"id":1659},"related-guides","Related guides",[24,1662,1663,1670,1677,1682,1689],{},[27,1664,1665,1666,41],{},"Compare the two approaches directly in ",[37,1667,1669],{"href":1668},"\u002Fintegration-database-and-service-testing\u002Ftest-data-factories-and-builders\u002Ffactory-boy-versus-plain-fixture-builders\u002F","factory_boy versus plain fixture builders",[27,1671,1672,1673,41],{},"Keep generated values reproducible with ",[37,1674,1676],{"href":1675},"\u002Fintegration-database-and-service-testing\u002Ftest-data-factories-and-builders\u002Fgenerating-reproducible-fake-data-with-faker\u002F","generating reproducible fake data with Faker",[27,1678,1679,1680,41],{},"Make sure the rows disappear afterwards using ",[37,1681,40],{"href":39},[27,1683,1684,1685,41],{},"When the goal is exploring the input space rather than filling fields, move to ",[37,1686,1688],{"href":1687},"\u002Fproperty-based-fuzz-testing-strategies\u002F","property-based and fuzz testing strategies",[27,1690,1691,1692,41],{},"Apply the same \"defaults plus one override\" idea to dependencies in ",[37,1693,1695],{"href":1694},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Fwiring-test-doubles-through-a-factory-function\u002F","wiring test doubles through a factory function",[10,1697,1698,1699],{},"← Back to ",[37,1700,1702],{"href":1701},"\u002Fintegration-database-and-service-testing\u002F","Integration, Database & Service Testing",[1704,1705,1706],"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);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":213,"searchDepth":226,"depth":226,"links":1708},[1709,1710,1711,1718,1719,1720,1721,1722,1723,1724,1725,1726],{"id":21,"depth":226,"text":22},{"id":62,"depth":226,"text":63},{"id":200,"depth":226,"text":201,"children":1712},[1713,1714,1715,1716,1717],{"id":205,"depth":233,"text":206},{"id":322,"depth":233,"text":323},{"id":377,"depth":233,"text":378},{"id":527,"depth":233,"text":528},{"id":662,"depth":233,"text":663},{"id":720,"depth":226,"text":721},{"id":818,"depth":226,"text":819},{"id":931,"depth":226,"text":932},{"id":1047,"depth":226,"text":1048},{"id":1208,"depth":226,"text":1209},{"id":1302,"depth":226,"text":1303},{"id":1503,"depth":226,"text":1504},{"id":1613,"depth":226,"text":1614},{"id":1659,"depth":226,"text":1660},"Make test preconditions readable: factory_boy traits and sequences, builder objects, deterministic Faker seeds, and setup that shows only what the test depends on.","md",{"slug":1730,"type":1731,"breadcrumb":1732,"datePublished":1733,"dateModified":1733,"faq":1734,"howto":1745},"test-data-factories-and-builders","topic","Data Factories","2026-09-18",[1735,1737,1739,1741,1743],{"q":1619,"a":1736},"Fixed or sequential defaults for anything the test might assert on, random only where variety genuinely helps and the value is never asserted. Random names and addresses are fine; random amounts, dates and identifiers produce tests that fail once a month with no way to reproduce. Where randomness is wanted deliberately, seed it and print the seed.",{"q":1625,"a":1738},"A fixture is a value pytest provides before the test runs; a factory is a callable the test invokes with its own arguments. Fixtures are right for shared infrastructure such as a session or a client, factories for the rows a specific test needs, because only the test knows which fields matter to it.",{"q":1631,"a":1740},"Make sub-objects lazy. factory_boy's SubFactory creates a related object on every call, so a three-level chain inserts a dozen rows per test. Use a trait or an explicit argument to opt into the deeper graph, and let the default build only what the object requires to be valid.",{"q":1643,"a":1742},"In the test directory by default, because they encode test conventions rather than product behaviour. Publish them as a package extra only when downstream consumers — another service's test suite, a plugin — genuinely need to build your models, and treat their signatures as public API from that point.",{"q":1649,"a":1744},"They must use the same session and must flush rather than commit. A factory that opens its own session writes outside the test's transaction and leaks rows; one that calls commit ends the savepoint. Bind the factory's session in a fixture and use flush to populate identifiers.",{"name":1746,"description":1747,"steps":1748},"How to build readable test data with factories","Give every field a safe default, override only what the test is about, and keep the generated data deterministic.",[1749,1752,1755,1758,1761],{"name":1750,"text":1751},"Define a factory per model with valid defaults","Every field gets a value that makes the object valid, using Sequence for anything that must be unique.",{"name":1753,"text":1754},"Bind the factory to the test session","Point the factory's session at the transactional fixture's session so rows participate in the test's rollback.",{"name":1756,"text":1757},"Express variations as traits","Add named traits for recurring states such as cancelled or expired instead of repeating keyword arguments across tests.",{"name":1759,"text":1760},"Keep related objects lazy","Use SubFactory only where the relation is required for validity, and opt into deeper graphs explicitly.",{"name":1762,"text":1763},"Seed any randomness","Configure Faker with a fixed seed per session and print it, so a failure caused by generated data can be reproduced.","\u002Fintegration-database-and-service-testing\u002Ftest-data-factories-and-builders",{"title":5,"description":1727},"integration-database-and-service-testing\u002Ftest-data-factories-and-builders\u002Findex","C8WLmBP7PcZACdGKsj1KDXL0bjdYZqx3GXO7OXRwXGk",1789718765722]