[{"data":1,"prerenderedAt":961},["ShallowReactive",2],{"page-\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Fwiring-test-doubles-through-a-factory-function\u002F":3},{"id":4,"title":5,"body":6,"description":924,"extension":925,"meta":926,"navigation":79,"path":957,"seo":958,"stem":959,"__hash__":960},"content\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Fwiring-test-doubles-through-a-factory-function\u002Findex.md","Wiring Test Doubles Through a Factory Function",{"type":7,"value":8,"toc":913},"minimark",[9,13,16,21,47,51,233,276,435,439,446,453,457,509,513,516,519,522,525,592,596,599,684,691,770,774,785,832,839,842,846,852,865,871,875,904,909],[10,11,12],"p",{},"A service with five collaborators needs five doubles in every test that constructs it — and most tests care about one. The result, repeated across a suite, is dozens of lines per test configuring a repository, a clock, a notifier, a payment gateway and a feature-flag client, with the one line that matters buried in the middle. A factory function inverts that: it builds the service with sensible fakes for everything, and each test passes only the collaborator it is actually about.",[10,14,15],{},"The pattern needs one change in production code — the service must receive its collaborators rather than constructing them — and one small function in the test support package. Everything else follows, including a large reduction in how much each test has to know about the parts of the system it is not testing. That reduction is the real payoff: a test that states only the collaborator it is about is a test whose intent is obvious from its first line, and one that no unrelated change to the service's wiring can break.",[17,18,20],"h2",{"id":19},"prerequisites","Prerequisites",[22,23,24,34,41],"ul",{},[25,26,27,28,33],"li",{},"A service that takes its dependencies through its constructor; see ",[29,30,32],"a",{"href":31},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002F","dependency injection for testability",".",[25,35,36,37,33],{},"Fakes for the main collaborators, as in ",[29,38,40],{"href":39},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fspies-fakes-and-hand-rolled-test-doubles\u002Fwriting-an-in-memory-fake-repository\u002F","writing an in-memory fake repository",[25,42,43,33],{},[44,45,46],"code",{},"pytest >= 8.0",[17,48,50],{"id":49},"solution","Solution",[52,53,58],"pre",{"className":54,"code":55,"language":56,"meta":57,"style":57},"language-python shiki shiki-themes github-light github-dark","# tests\u002Fsupport\u002Fbilling.py\nfrom dataclasses import dataclass\n\nfrom myapp.billing import BillingService\nfrom tests.support.fakes import (FakeClock, FakeGateway, FakeNotifier,\n                                 FakeOrderRepository, StaticFlags)\n\n\n@dataclass\nclass Wired:\n    service: BillingService\n    repo: FakeOrderRepository\n    clock: FakeClock\n    gateway: FakeGateway\n    notifier: FakeNotifier\n\n\ndef make_billing(**overrides) -> Wired:\n    \"\"\"A BillingService with a fake for every collaborator unless overridden.\"\"\"\n    parts = dict(\n        repo=FakeOrderRepository(),\n        clock=FakeClock(),\n        gateway=FakeGateway(),\n        notifier=FakeNotifier(),\n        flags=StaticFlags(),\n    )\n    parts.update(overrides)                     # the test's choices win\n    service = BillingService(**parts)\n    return Wired(service=service, **{k: v for k, v in parts.items() if k != \"flags\"})\n","python","",[44,59,60,68,74,81,87,93,99,104,109,115,121,127,133,139,145,151,156,161,167,173,179,185,191,197,203,209,215,221,227],{"__ignoreMap":57},[61,62,65],"span",{"class":63,"line":64},"line",1,[61,66,67],{},"# tests\u002Fsupport\u002Fbilling.py\n",[61,69,71],{"class":63,"line":70},2,[61,72,73],{},"from dataclasses import dataclass\n",[61,75,77],{"class":63,"line":76},3,[61,78,80],{"emptyLinePlaceholder":79},true,"\n",[61,82,84],{"class":63,"line":83},4,[61,85,86],{},"from myapp.billing import BillingService\n",[61,88,90],{"class":63,"line":89},5,[61,91,92],{},"from tests.support.fakes import (FakeClock, FakeGateway, FakeNotifier,\n",[61,94,96],{"class":63,"line":95},6,[61,97,98],{},"                                 FakeOrderRepository, StaticFlags)\n",[61,100,102],{"class":63,"line":101},7,[61,103,80],{"emptyLinePlaceholder":79},[61,105,107],{"class":63,"line":106},8,[61,108,80],{"emptyLinePlaceholder":79},[61,110,112],{"class":63,"line":111},9,[61,113,114],{},"@dataclass\n",[61,116,118],{"class":63,"line":117},10,[61,119,120],{},"class Wired:\n",[61,122,124],{"class":63,"line":123},11,[61,125,126],{},"    service: BillingService\n",[61,128,130],{"class":63,"line":129},12,[61,131,132],{},"    repo: FakeOrderRepository\n",[61,134,136],{"class":63,"line":135},13,[61,137,138],{},"    clock: FakeClock\n",[61,140,142],{"class":63,"line":141},14,[61,143,144],{},"    gateway: FakeGateway\n",[61,146,148],{"class":63,"line":147},15,[61,149,150],{},"    notifier: FakeNotifier\n",[61,152,154],{"class":63,"line":153},16,[61,155,80],{"emptyLinePlaceholder":79},[61,157,159],{"class":63,"line":158},17,[61,160,80],{"emptyLinePlaceholder":79},[61,162,164],{"class":63,"line":163},18,[61,165,166],{},"def make_billing(**overrides) -> Wired:\n",[61,168,170],{"class":63,"line":169},19,[61,171,172],{},"    \"\"\"A BillingService with a fake for every collaborator unless overridden.\"\"\"\n",[61,174,176],{"class":63,"line":175},20,[61,177,178],{},"    parts = dict(\n",[61,180,182],{"class":63,"line":181},21,[61,183,184],{},"        repo=FakeOrderRepository(),\n",[61,186,188],{"class":63,"line":187},22,[61,189,190],{},"        clock=FakeClock(),\n",[61,192,194],{"class":63,"line":193},23,[61,195,196],{},"        gateway=FakeGateway(),\n",[61,198,200],{"class":63,"line":199},24,[61,201,202],{},"        notifier=FakeNotifier(),\n",[61,204,206],{"class":63,"line":205},25,[61,207,208],{},"        flags=StaticFlags(),\n",[61,210,212],{"class":63,"line":211},26,[61,213,214],{},"    )\n",[61,216,218],{"class":63,"line":217},27,[61,219,220],{},"    parts.update(overrides)                     # the test's choices win\n",[61,222,224],{"class":63,"line":223},28,[61,225,226],{},"    service = BillingService(**parts)\n",[61,228,230],{"class":63,"line":229},29,[61,231,232],{},"    return Wired(service=service, **{k: v for k, v in parts.items() if k != \"flags\"})\n",[52,234,236],{"className":54,"code":235,"language":56,"meta":57,"style":57},"def test_declined_payment_notifies_the_customer():\n    # Only the gateway matters here; everything else is a sensible default.\n    wired = make_billing(gateway=FakeGateway(decline_all=True))\n    wired.repo.add(an_order(id=\"ord_1\", customer_id=\"cus_1\"))\n\n    wired.service.charge(\"ord_1\")\n\n    assert wired.notifier.sent == [(\"cus_1\", \"payment_declined\")]\n",[44,237,238,243,248,253,258,262,267,271],{"__ignoreMap":57},[61,239,240],{"class":63,"line":64},[61,241,242],{},"def test_declined_payment_notifies_the_customer():\n",[61,244,245],{"class":63,"line":70},[61,246,247],{},"    # Only the gateway matters here; everything else is a sensible default.\n",[61,249,250],{"class":63,"line":76},[61,251,252],{},"    wired = make_billing(gateway=FakeGateway(decline_all=True))\n",[61,254,255],{"class":63,"line":83},[61,256,257],{},"    wired.repo.add(an_order(id=\"ord_1\", customer_id=\"cus_1\"))\n",[61,259,260],{"class":63,"line":89},[61,261,80],{"emptyLinePlaceholder":79},[61,263,264],{"class":63,"line":95},[61,265,266],{},"    wired.service.charge(\"ord_1\")\n",[61,268,269],{"class":63,"line":101},[61,270,80],{"emptyLinePlaceholder":79},[61,272,273],{"class":63,"line":106},[61,274,275],{},"    assert wired.notifier.sent == [(\"cus_1\", \"payment_declined\")]\n",[277,278,281,431],"figure",{"className":279},[280],"diagram",[282,283,290,291,290,295,290,299,290,317,290,325,290,335,290,344,290,350,290,356,290,360,290,365,290,369,290,373,290,381,290,386,290,390,290,394,290,401,290,408,290,415,290,420,290,424,290,427],"svg",{"viewBox":284,"role":285,"ariaLabelledBy":286,"xmlns":289},"0 0 820 262","img",[287,288],"wf-t","wf-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[292,293,294],"title",{"id":287},"A factory with defaults and one override",[296,297,298],"desc",{"id":288},"The make_billing factory holds a default fake for each of five collaborators. A test passes one override, a declining gateway. The factory merges it over the defaults, constructs the service, and returns the service together with every collaborator so the test can inspect the notifier it never configured.",[300,301,302,303,290],"defs",{},"\n    ",[304,305,312],"marker",{"id":306,"viewBox":307,"refX":308,"refY":309,"markerWidth":310,"markerHeight":310,"orient":311},"wf-a","0 0 10 10","9","5","7","auto-start-reverse",[313,314],"path",{"d":315,"fill":316},"M0 0 L10 5 L0 10 z","#81b29a",[318,319],"rect",{"x":320,"y":320,"width":321,"height":322,"rx":323,"fill":324},"0","820","262","14","#fffdf8",[326,327,334],"text",{"x":328,"y":329,"textAnchor":330,"fontSize":331,"fontWeight":332,"fill":333},"410","28","middle","16","700","#3d405b","Defaults for everything, overrides for what matters",[318,336],{"x":337,"y":338,"width":339,"height":340,"rx":341,"fill":342,"stroke":316,"strokeWidth":343},"26","52","250","186","12","#e6f0ea","2",[326,345,349],{"x":346,"y":347,"textAnchor":330,"fontSize":348,"fontWeight":332,"fill":333},"151","78","12.5","factory defaults",[326,351,355],{"x":352,"y":353,"fontSize":354,"fill":333},"44","106","11","repo = FakeOrderRepository()",[326,357,359],{"x":352,"y":358,"fontSize":354,"fill":333},"128","clock = FakeClock()",[326,361,364],{"x":352,"y":362,"fontSize":354,"fill":363},"150","#8f3d22","gateway = FakeGateway()",[326,366,368],{"x":352,"y":367,"fontSize":354,"fill":333},"172","notifier = FakeNotifier()",[326,370,372],{"x":352,"y":371,"fontSize":354,"fill":333},"194","flags = StaticFlags()",[318,374],{"x":375,"y":376,"width":377,"height":378,"rx":354,"fill":379,"stroke":380,"strokeWidth":343},"296","96","220","80","#fbe9e3","#e07a5f",[326,382,385],{"x":383,"y":384,"textAnchor":330,"fontSize":341,"fontWeight":332,"fill":333},"406","124","test override",[326,387,389],{"x":383,"y":388,"textAnchor":330,"fontSize":354,"fill":333},"146","gateway=FakeGateway(",[326,391,393],{"x":383,"y":392,"textAnchor":330,"fontSize":354,"fill":333},"162","decline_all=True)",[63,395],{"x1":396,"y1":397,"x2":398,"y2":399,"stroke":316,"strokeWidth":400},"280","145","292","140","1.6",[63,402],{"x1":403,"y1":404,"x2":405,"y2":404,"stroke":316,"strokeWidth":406,"markerEnd":407},"520","136","560","1.8","url(#wf-a)",[318,409],{"x":410,"y":411,"width":412,"height":358,"rx":341,"fill":413,"stroke":414,"strokeWidth":343},"566","72","228","#f7f0da","#f2cc8f",[326,416,419],{"x":417,"y":418,"textAnchor":330,"fontSize":348,"fontWeight":332,"fill":333},"680","98","Wired",[326,421,423],{"x":422,"y":384,"fontSize":354,"fill":333},"584","service — fully assembled",[326,425,426],{"x":422,"y":388,"fontSize":354,"fill":333},"repo, clock, gateway,",[326,428,430],{"x":422,"y":429,"fontSize":354,"fill":333},"168","notifier — for inspection",[432,433,434],"figcaption",{},"The test mentions one collaborator. The notifier it asserts on was never configured by the test at all — the default fake records what it was asked to send.",[17,436,438],{"id":437},"why-this-works","Why this works",[10,440,441,442,445],{},"The factory encodes the assembly of the service once, with a fake for every dependency. ",[44,443,444],{},"parts.update(overrides)"," lets a test replace any subset by keyword, and anything it does not mention falls back to the default. Because the defaults are fakes with real behaviour rather than bare mocks, the service works end to end in every test without configuration: orders can be saved and loaded, notifications recorded, time advanced.",[10,447,448,449,452],{},"Returning the collaborators alongside the service is what makes assertions natural. The test did not create the notifier, but it can still inspect ",[44,450,451],{},"wired.notifier.sent",", because the factory hands back every part it used. Without that, the test would have to create every collaborator it might want to assert on, which reintroduces the boilerplate the factory exists to remove.",[17,454,456],{"id":455},"edge-cases-and-failure-modes","Edge cases and failure modes",[22,458,459,474,480,486,500],{},[25,460,461,469,470,473],{},[462,463,464,465,468],"strong",{},"Bare ",[44,466,467],{},"Mock()"," defaults."," A mock default returns mock objects, so an untouched collaborator produces confusing downstream failures. Use fakes, or at least ",[44,471,472],{},"create_autospec(..., spec_set=True)"," with realistic return values.",[25,475,476,479],{},[462,477,478],{},"Shared default instances."," Defaults created at module level are shared between tests, so state leaks. Construct defaults inside the factory, fresh per call.",[25,481,482,485],{},[462,483,484],{},"A factory per test file."," Several slightly different factories drift apart. Keep one per service, in the test-support package.",[25,487,488,491,492,495,496,499],{},[462,489,490],{},"Overrides with typos."," ",[44,493,494],{},"make_billing(gatewy=…)"," would silently pass an unknown keyword to the constructor and fail with a confusing error. Check ",[44,497,498],{},"overrides.keys()"," against the known parts and raise with a clear message.",[25,501,502,491,505,508],{},[462,503,504],{},"Growing the factory with scenario flags.",[44,506,507],{},"make_billing(declining=True, expired_card=True)"," turns the factory into a scenario engine. Keep it about wiring; express scenarios through the fakes' own constructors.",[17,510,512],{"id":511},"what-the-factory-does-to-a-test-suite-over-time","What the factory does to a test suite over time",[10,514,515],{},"The immediate effect is shorter tests. The longer-term effect is more interesting: the factory becomes the single place where the service's dependency graph is expressed for tests, and that changes how the suite responds to change.",[10,517,518],{},"When the service gains a new collaborator — an audit logger, say — without a factory, every test that constructs the service must be edited to pass one, and a large suite turns a one-line production change into a two-hundred-file diff. With a factory, the new collaborator gets a fake default in one place, every existing test keeps passing unchanged, and only the tests that are actually about auditing mention it. The cost of adding a dependency drops from proportional-to-the-suite to constant.",[10,520,521],{},"The same holds in reverse. Removing a collaborator, renaming a constructor parameter, or splitting one dependency into two all become edits to the factory and to the handful of tests that override that specific part. Tests that do not care about a collaborator are, by construction, insulated from changes to it — which is exactly the property a test suite should have, and the one hand-assembled services destroy.",[10,523,524],{},"A useful measure of whether the factory is doing its job is how many tests in the suite construct the service directly rather than through it. That number should be close to zero, and the exceptions should be the tests of the factory's defaults themselves. When direct construction creeps back in — usually because someone needed an unusual combination and found it quicker to write it out — it is worth adding the combination as an override instead, before the pattern of bypassing the factory spreads.",[277,526,528,589],{"className":527},[280],[282,529,290,534,290,537,290,540,290,544,290,549,290,554,290,559,290,564,290,568,290,572,290,575,290,579,290,583,290,586],{"viewBox":530,"role":285,"ariaLabelledBy":531,"xmlns":289},"0 0 800 236",[532,533],"chg-t","chg-d",[292,535,536],{"id":532},"Cost of adding a collaborator with and without a factory",[296,538,539],{"id":533},"When a service gains a new dependency, a suite that constructs the service by hand in two hundred tests needs two hundred edits. A suite that builds it through a factory needs one edit to add a fake default, and only the tests about the new dependency mention it.",[318,541],{"x":320,"y":320,"width":542,"height":543,"rx":323,"fill":324},"800","236",[326,545,548],{"x":546,"y":329,"textAnchor":330,"fontSize":547,"fontWeight":332,"fill":333},"400","15.5","The service gains an audit logger",[318,550],{"x":337,"y":551,"width":552,"height":553,"rx":341,"fill":379,"stroke":380,"strokeWidth":343},"50","360","164",[326,555,558],{"x":556,"y":557,"textAnchor":330,"fontSize":348,"fontWeight":332,"fill":333},"206","76","hand-assembled in each test",[326,560,563],{"x":556,"y":561,"textAnchor":330,"fontSize":562,"fontWeight":332,"fill":363},"120","22","200 edits",[326,565,567],{"x":556,"y":566,"textAnchor":330,"fontSize":354,"fill":333},"156","every test that builds the service",[326,569,571],{"x":556,"y":570,"textAnchor":330,"fontSize":354,"fill":333},"178","must pass the new dependency",[318,573],{"x":574,"y":551,"width":552,"height":553,"rx":341,"fill":342,"stroke":316,"strokeWidth":343},"414",[326,576,578],{"x":577,"y":557,"textAnchor":330,"fontSize":348,"fontWeight":332,"fill":333},"594","built through make_billing",[326,580,582],{"x":577,"y":561,"textAnchor":330,"fontSize":562,"fontWeight":332,"fill":581},"#2a5f49","1 edit",[326,584,585],{"x":577,"y":566,"textAnchor":330,"fontSize":354,"fill":333},"a fake default in the factory;",[326,587,588],{"x":577,"y":570,"textAnchor":330,"fontSize":354,"fill":333},"only audit tests mention it",[432,590,591],{},"Tests that do not care about a collaborator should never have to change when it does. The factory is what makes that true.",[17,593,595],{"id":594},"providing-the-factory-through-a-fixture","Providing the factory through a fixture",[10,597,598],{},"Some defaults need setup the factory cannot do on its own — a temporary directory for a file-backed store, a database session for the integration variant. The clean arrangement is a fixture that returns the factory itself, closed over whatever setup it needs, so tests still call it with their overrides.",[52,600,602],{"className":54,"code":601,"language":56,"meta":57,"style":57},"import pytest\n\n\n@pytest.fixture\ndef make_billing_with_db(db_session):\n    def factory(**overrides):\n        overrides.setdefault(\"repo\", SqlOrderRepository(db_session))\n        return make_billing(**overrides)\n    return factory\n\n\n@pytest.mark.integration\ndef test_charge_persists_the_payment(make_billing_with_db):\n    wired = make_billing_with_db()\n    wired.repo.add(an_order(id=\"ord_1\"))\n    wired.service.charge(\"ord_1\")\n    assert wired.repo.get(\"ord_1\").status == \"paid\"\n",[44,603,604,609,613,617,622,627,632,637,642,647,651,655,660,665,670,675,679],{"__ignoreMap":57},[61,605,606],{"class":63,"line":64},[61,607,608],{},"import pytest\n",[61,610,611],{"class":63,"line":70},[61,612,80],{"emptyLinePlaceholder":79},[61,614,615],{"class":63,"line":76},[61,616,80],{"emptyLinePlaceholder":79},[61,618,619],{"class":63,"line":83},[61,620,621],{},"@pytest.fixture\n",[61,623,624],{"class":63,"line":89},[61,625,626],{},"def make_billing_with_db(db_session):\n",[61,628,629],{"class":63,"line":95},[61,630,631],{},"    def factory(**overrides):\n",[61,633,634],{"class":63,"line":101},[61,635,636],{},"        overrides.setdefault(\"repo\", SqlOrderRepository(db_session))\n",[61,638,639],{"class":63,"line":106},[61,640,641],{},"        return make_billing(**overrides)\n",[61,643,644],{"class":63,"line":111},[61,645,646],{},"    return factory\n",[61,648,649],{"class":63,"line":117},[61,650,80],{"emptyLinePlaceholder":79},[61,652,653],{"class":63,"line":123},[61,654,80],{"emptyLinePlaceholder":79},[61,656,657],{"class":63,"line":129},[61,658,659],{},"@pytest.mark.integration\n",[61,661,662],{"class":63,"line":135},[61,663,664],{},"def test_charge_persists_the_payment(make_billing_with_db):\n",[61,666,667],{"class":63,"line":141},[61,668,669],{},"    wired = make_billing_with_db()\n",[61,671,672],{"class":63,"line":147},[61,673,674],{},"    wired.repo.add(an_order(id=\"ord_1\"))\n",[61,676,677],{"class":63,"line":153},[61,678,266],{},[61,680,681],{"class":63,"line":158},[61,682,683],{},"    assert wired.repo.get(\"ord_1\").status == \"paid\"\n",[10,685,686,687,690],{},"The unit tests keep calling ",[44,688,689],{},"make_billing"," with in-memory fakes; the integration tests call the fixture-provided variant, which swaps in the real repository and nothing else. Both share one assembly path, so a change to how the service is wired shows up in both at once rather than drifting between a unit-test factory and a hand-built integration setup.",[277,692,694,767],{"className":693},[280],[282,695,290,700,290,703,290,706,290,713,290,716,290,719,290,724,290,728,290,733,290,739,290,743,290,749,290,754,290,757,290,761,290,764],{"viewBox":696,"role":285,"ariaLabelledBy":697,"xmlns":289},"0 0 800 226",[698,699],"ff-t","ff-d",[292,701,702],{"id":698},"One assembly path for unit and integration tests",[296,704,705],{"id":699},"The make_billing factory is used directly by unit tests with all-fake defaults. A fixture wraps it for integration tests, overriding only the repository with the real SQL implementation. Both paths build the service the same way, so wiring changes affect them together.",[300,707,302,708,290],{},[304,709,711],{"id":710,"viewBox":307,"refX":308,"refY":309,"markerWidth":310,"markerHeight":310,"orient":311},"ff-a",[313,712],{"d":315,"fill":316},[318,714],{"x":320,"y":320,"width":542,"height":715,"rx":323,"fill":324},"226",[326,717,718],{"x":546,"y":329,"textAnchor":330,"fontSize":547,"fontWeight":332,"fill":333},"Same wiring, different defaults",[318,720],{"x":721,"y":722,"width":377,"height":723,"rx":354,"fill":413,"stroke":414,"strokeWidth":343},"290","56","60",[326,725,727],{"x":546,"y":726,"textAnchor":330,"fontSize":341,"fontWeight":332,"fill":333},"82","make_billing(**overrides)",[326,729,732],{"x":546,"y":730,"textAnchor":330,"fontSize":354,"fill":731},"102","#8a5a00","the single assembly path",[63,734],{"x1":735,"y1":561,"x2":736,"y2":737,"stroke":316,"strokeWidth":406,"markerEnd":738},"340","200","148","url(#ff-a)",[63,740],{"x1":741,"y1":561,"x2":742,"y2":737,"stroke":316,"strokeWidth":406,"markerEnd":738},"460","600",[318,744],{"x":745,"y":746,"width":747,"height":722,"rx":748,"fill":342,"stroke":316,"strokeWidth":343},"40","152","320","10",[326,750,753],{"x":736,"y":751,"textAnchor":330,"fontSize":752,"fontWeight":332,"fill":333},"176","11.5","unit tests",[326,755,756],{"x":736,"y":371,"textAnchor":330,"fontSize":354,"fill":581},"all fakes, microseconds",[318,758],{"x":759,"y":746,"width":747,"height":722,"rx":748,"fill":760,"stroke":333,"strokeWidth":400},"440","#f4f1de",[326,762,763],{"x":742,"y":751,"textAnchor":330,"fontSize":752,"fontWeight":332,"fill":333},"integration fixture",[326,765,766],{"x":742,"y":371,"textAnchor":330,"fontSize":354,"fill":333},"real repo, everything else fake",[432,768,769],{},"The integration variant overrides exactly one part, so it tests the real repository in the context of an otherwise identical service.",[17,771,773],{"id":772},"validating-overrides","Validating overrides",[10,775,776,777,780,781,784],{},"One small addition makes the factory noticeably friendlier: rejecting overrides it does not recognise. Because the overrides are forwarded to the service's constructor, a misspelt keyword produces a ",[44,778,779],{},"TypeError"," from deep inside ",[44,782,783],{},"BillingService.__init__",", naming an unexpected argument without saying where it came from. Checking the keys up front turns that into a precise message at the call site.",[52,786,788],{"className":54,"code":787,"language":56,"meta":57,"style":57},"KNOWN = {\"repo\", \"clock\", \"gateway\", \"notifier\", \"flags\"}\n\n\ndef make_billing(**overrides) -> Wired:\n    unknown = set(overrides) - KNOWN\n    if unknown:\n        raise TypeError(f\"make_billing got unknown overrides {sorted(unknown)}; \"\n                        f\"expected some of {sorted(KNOWN)}\")\n    ...\n",[44,789,790,795,799,803,807,812,817,822,827],{"__ignoreMap":57},[61,791,792],{"class":63,"line":64},[61,793,794],{},"KNOWN = {\"repo\", \"clock\", \"gateway\", \"notifier\", \"flags\"}\n",[61,796,797],{"class":63,"line":70},[61,798,80],{"emptyLinePlaceholder":79},[61,800,801],{"class":63,"line":76},[61,802,80],{"emptyLinePlaceholder":79},[61,804,805],{"class":63,"line":83},[61,806,166],{},[61,808,809],{"class":63,"line":89},[61,810,811],{},"    unknown = set(overrides) - KNOWN\n",[61,813,814],{"class":63,"line":95},[61,815,816],{},"    if unknown:\n",[61,818,819],{"class":63,"line":101},[61,820,821],{},"        raise TypeError(f\"make_billing got unknown overrides {sorted(unknown)}; \"\n",[61,823,824],{"class":63,"line":106},[61,825,826],{},"                        f\"expected some of {sorted(KNOWN)}\")\n",[61,828,829],{"class":63,"line":111},[61,830,831],{},"    ...\n",[10,833,834,835,838],{},"The known set doubles as documentation of what the service depends on, and keeping it next to the defaults means adding a collaborator is one edit to one place. It is the same principle as ",[44,836,837],{},"spec_set"," on a mock: fail at the moment a name is wrong, with the wrong name in the message, rather than somewhere downstream where the mistake is no longer visible.",[10,840,841],{},"The last habit worth adopting is to give the factory's defaults the most boring behaviour available. A gateway that approves every charge, a notifier that records and succeeds, a clock fixed at a known instant, flags all at their production defaults. Boring defaults mean a test that does not mention a collaborator gets the happy path from it, and every test that needs something unusual says so explicitly in its override — which is exactly where a reader will look for it.",[17,843,845],{"id":844},"frequently-asked-questions","Frequently Asked Questions",[10,847,848,851],{},[462,849,850],{},"How is a factory function different from a pytest fixture?","\nA fixture is built before the test runs and cannot take per-test arguments without indirection. A factory function is called by the test with the overrides it needs, so each test decides which collaborator to replace. The factory can itself be provided by a fixture when it needs setup.",[10,853,854,857,858,861,862,864],{},[462,855,856],{},"Should the factory's defaults be mocks or fakes?","\nFakes wherever one exists. A fake with real behaviour makes the default service usable in any test without configuration; a default ",[44,859,860],{},"Mock"," returns ",[44,863,860],{}," objects that often cause confusing failures in tests that never meant to involve that collaborator.",[10,866,867,870],{},[462,868,869],{},"Does production code need to change for this?","\nOnly to accept its collaborators rather than constructing them. Once a service takes its dependencies in its constructor, the factory is purely test-support code that assembles it with fakes by default.",[17,872,874],{"id":873},"related","Related",[22,876,877,883,890,897],{},[25,878,879,882],{},[29,880,881],{"href":31},"Dependency Injection for Testability"," — the constructor change this pattern depends on.",[25,884,885,889],{},[29,886,888],{"href":887},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Finjecting-fakes-vs-mocks-in-constructors\u002F","Injecting Fakes vs Mocks in Constructors"," — choosing what the defaults should be.",[25,891,892,896],{},[29,893,895],{"href":894},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Finjecting-a-clock-instead-of-patching-datetime\u002F","Injecting a Clock Instead of Patching datetime"," — the FakeClock used as a default here.",[25,898,899,903],{},[29,900,902],{"href":901},"\u002Fintegration-database-and-service-testing\u002Ftest-data-factories-and-builders\u002F","Test Data Factories & Builders"," — the same defaults-plus-overrides idea for data.",[10,905,906,907],{},"← Back to ",[29,908,881],{"href":31},[910,911,912],"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":57,"searchDepth":70,"depth":70,"links":914},[915,916,917,918,919,920,921,922,923],{"id":19,"depth":70,"text":20},{"id":49,"depth":70,"text":50},{"id":437,"depth":70,"text":438},{"id":455,"depth":70,"text":456},{"id":511,"depth":70,"text":512},{"id":594,"depth":70,"text":595},{"id":772,"depth":70,"text":773},{"id":844,"depth":70,"text":845},{"id":873,"depth":70,"text":874},"Build services for tests through a single factory with sensible fake defaults and keyword overrides, so each test states only the collaborator it cares about.","md",{"slug":927,"type":928,"breadcrumb":929,"datePublished":930,"dateModified":930,"faq":931,"howto":938},"wiring-test-doubles-through-a-factory-function","article","Factory Wiring","2026-09-18",[932,934,936],{"q":850,"a":933},"A fixture is built before the test runs and cannot take per-test arguments without indirection. A factory function is called by the test with the overrides it needs, so each test decides which collaborator to replace. The factory can itself be provided by a fixture when it needs setup.",{"q":856,"a":935},"Fakes wherever one exists. A fake with real behaviour makes the default service usable in any test without configuration; a default Mock returns Mock objects that often cause confusing failures in tests that never meant to involve that collaborator.",{"q":869,"a":937},"Only to accept its collaborators rather than constructing them. Once a service takes its dependencies in its constructor, the factory is purely test-support code that assembles it with fakes by default.",{"name":939,"description":940,"steps":941},"How to wire test doubles through a factory","Give the service constructor its dependencies, write one test factory with fake defaults, and let each test override only what it cares about.",[942,945,948,951,954],{"name":943,"text":944},"Make the constructor take its collaborators","Change the service to receive repository, clock, notifier and similar dependencies as parameters.",{"name":946,"text":947},"Write one factory with fake defaults","Create make_service(**overrides) that builds every collaborator with a fake unless overridden.",{"name":949,"text":950},"Return the collaborators alongside the service","Expose the defaults so tests can inspect the fake a service used without re-creating it.",{"name":952,"text":953},"Override only what the test is about","Pass exactly the collaborator the test needs to control and let everything else default.",{"name":955,"text":956},"Keep the factory next to the fakes","Store it in the test-support module so every test builds services the same way.","\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Fwiring-test-doubles-through-a-factory-function",{"title":5,"description":924},"advanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Fwiring-test-doubles-through-a-factory-function\u002Findex","dVGmTr6VxrkPK5X5haZhz88EANVCqdAsIeLU-csAO7E",1789718768462]