[{"data":1,"prerenderedAt":871},["ShallowReactive",2],{"page-\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Fparametrizing-fixtures-with-params-and-ids\u002F":3},{"id":4,"title":5,"body":6,"description":834,"extension":835,"meta":836,"navigation":82,"path":867,"seo":868,"stem":869,"__hash__":870},"content\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Fparametrizing-fixtures-with-params-and-ids\u002Findex.md","Parametrizing Fixtures with params and ids",{"type":7,"value":8,"toc":823},"minimark",[9,23,26,31,56,60,194,202,217,367,371,382,385,389,433,437,440,464,485,532,535,609,613,616,622,625,629,632,635,665,671,674,746,750,759,776,782,786,814,819],[10,11,12,13,17,18,22],"p",{},"Some variation belongs to the environment rather than to any one test. Every repository test should run against both the SQL and the in-memory implementation; every serializer test should run for JSON and MessagePack; every client test should run with and without a proxy. Writing ",[14,15,16],"code",{},"@pytest.mark.parametrize"," on each of those tests duplicates the list and drifts. Parametrizing the ",[19,20,21],"em",{},"fixture"," states the variation once, and every test that depends on it — directly or through other fixtures — runs once per value automatically.",[10,24,25],{},"That reach is the feature and the hazard. A parametrized fixture three levels down the dependency graph silently multiplies every test above it, and a suite can double in runtime from a one-line change nobody thought of as a performance change. Explicit ids and a quick count keep both the benefit and the cost visible.",[27,28,30],"h2",{"id":29},"prerequisites","Prerequisites",[32,33,34,41,49],"ul",{},[35,36,37,40],"li",{},[14,38,39],{},"pytest >= 8.0",".",[35,42,43,44,40],{},"The fixture scoping model from ",[45,46,48],"a",{"href":47},"\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002F","mastering pytest fixtures",[35,50,51,52,40],{},"Stable, readable parameter ids — the reasoning is in ",[45,53,55],{"href":54},"\u002Fadvanced-pytest-architecture-configuration\u002Fadvanced-parametrization-techniques\u002Fgenerating-readable-test-ids\u002F","generating readable test IDs",[27,57,59],{"id":58},"solution","Solution",[61,62,67],"pre",{"className":63,"code":64,"language":65,"meta":66,"style":66},"language-python shiki shiki-themes github-light github-dark","import pytest\n\nfrom myapp.repositories import InMemoryInvoiceRepository, SqlInvoiceRepository\n\n\n@pytest.fixture(\n    params=[\n        pytest.param(\"memory\", id=\"memory\"),\n        pytest.param(\"sql\", id=\"sql\", marks=pytest.mark.integration),\n    ]\n)\ndef invoice_repository(request, db_session):\n    # request.param carries the current value; each dependent test runs once per value.\n    if request.param == \"memory\":\n        return InMemoryInvoiceRepository()\n    return SqlInvoiceRepository(db_session)\n\n\ndef test_saved_invoice_is_retrievable(invoice_repository, an_invoice):\n    invoice_repository.save(an_invoice)\n    assert invoice_repository.get(an_invoice.id) == an_invoice\n","python","",[14,68,69,77,84,90,95,100,106,112,118,124,130,136,142,148,154,160,166,171,176,182,188],{"__ignoreMap":66},[70,71,74],"span",{"class":72,"line":73},"line",1,[70,75,76],{},"import pytest\n",[70,78,80],{"class":72,"line":79},2,[70,81,83],{"emptyLinePlaceholder":82},true,"\n",[70,85,87],{"class":72,"line":86},3,[70,88,89],{},"from myapp.repositories import InMemoryInvoiceRepository, SqlInvoiceRepository\n",[70,91,93],{"class":72,"line":92},4,[70,94,83],{"emptyLinePlaceholder":82},[70,96,98],{"class":72,"line":97},5,[70,99,83],{"emptyLinePlaceholder":82},[70,101,103],{"class":72,"line":102},6,[70,104,105],{},"@pytest.fixture(\n",[70,107,109],{"class":72,"line":108},7,[70,110,111],{},"    params=[\n",[70,113,115],{"class":72,"line":114},8,[70,116,117],{},"        pytest.param(\"memory\", id=\"memory\"),\n",[70,119,121],{"class":72,"line":120},9,[70,122,123],{},"        pytest.param(\"sql\", id=\"sql\", marks=pytest.mark.integration),\n",[70,125,127],{"class":72,"line":126},10,[70,128,129],{},"    ]\n",[70,131,133],{"class":72,"line":132},11,[70,134,135],{},")\n",[70,137,139],{"class":72,"line":138},12,[70,140,141],{},"def invoice_repository(request, db_session):\n",[70,143,145],{"class":72,"line":144},13,[70,146,147],{},"    # request.param carries the current value; each dependent test runs once per value.\n",[70,149,151],{"class":72,"line":150},14,[70,152,153],{},"    if request.param == \"memory\":\n",[70,155,157],{"class":72,"line":156},15,[70,158,159],{},"        return InMemoryInvoiceRepository()\n",[70,161,163],{"class":72,"line":162},16,[70,164,165],{},"    return SqlInvoiceRepository(db_session)\n",[70,167,169],{"class":72,"line":168},17,[70,170,83],{"emptyLinePlaceholder":82},[70,172,174],{"class":72,"line":173},18,[70,175,83],{"emptyLinePlaceholder":82},[70,177,179],{"class":72,"line":178},19,[70,180,181],{},"def test_saved_invoice_is_retrievable(invoice_repository, an_invoice):\n",[70,183,185],{"class":72,"line":184},20,[70,186,187],{},"    invoice_repository.save(an_invoice)\n",[70,189,191],{"class":72,"line":190},21,[70,192,193],{},"    assert invoice_repository.get(an_invoice.id) == an_invoice\n",[61,195,200],{"className":196,"code":198,"language":199,"meta":66},[197],"language-text","tests\u002Ftest_repo.py::test_saved_invoice_is_retrievable[memory] PASSED\ntests\u002Ftest_repo.py::test_saved_invoice_is_retrievable[sql]    PASSED\n","text",[14,201,198],{"__ignoreMap":66},[10,203,204,205,208,209,212,213,216],{},"The ",[14,206,207],{},"integration"," mark on the ",[14,210,211],{},"sql"," parameter is the detail that makes this practical: ",[14,214,215],{},"pytest -m \"not integration\""," runs only the in-memory variant in the fast suite, and the merge queue runs both, with one fixture and one test body.",[218,219,222,359],"figure",{"className":220},[221],"diagram",[223,224,231,232,231,236,231,240,231,258,231,266,231,274,231,284,231,290,231,295,231,303,231,307,231,316,231,322,231,327,231,330,231,334,231,339,231,342,231,350,231,355],"svg",{"viewBox":225,"role":226,"ariaLabelledBy":227,"xmlns":230},"0 0 820 262","img",[228,229],"fp-t","fp-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[233,234,235],"title",{"id":228},"How a parametrized fixture multiplies dependent tests",[237,238,239],"desc",{"id":229},"A repository fixture with two params, memory and sql, is requested by three tests directly and by a service fixture that two further tests request. All five tests are therefore collected twice, producing ten items, each with the parameter id in its name.",[241,242,243,244,231],"defs",{},"\n    ",[245,246,253],"marker",{"id":247,"viewBox":248,"refX":249,"refY":250,"markerWidth":251,"markerHeight":251,"orient":252},"fp-a","0 0 10 10","9","5","7","auto-start-reverse",[254,255],"path",{"d":256,"fill":257},"M0 0 L10 5 L0 10 z","#3d405b",[259,260],"rect",{"x":261,"y":261,"width":262,"height":263,"rx":264,"fill":265},"0","820","262","14","#fffdf8",[199,267,273],{"x":268,"y":269,"textAnchor":270,"fontSize":271,"fontWeight":272,"fill":257},"410","28","middle","16","700","One fixture's params reach every test above it",[259,275],{"x":276,"y":277,"width":278,"height":279,"rx":280,"fill":281,"stroke":282,"strokeWidth":283},"30","104","190","64","11","#f7f0da","#f2cc8f","2",[199,285,289],{"x":286,"y":287,"textAnchor":270,"fontSize":288,"fontWeight":272,"fill":257},"125","130","12","invoice_repository",[199,291,294],{"x":286,"y":292,"textAnchor":270,"fontSize":280,"fill":293},"150","#8a5a00","params: memory, sql",[72,296],{"x1":297,"y1":298,"x2":299,"y2":300,"stroke":257,"strokeWidth":301,"markerEnd":302},"224","120","300","78","1.5","url(#fp-a)",[72,304],{"x1":297,"y1":305,"x2":299,"y2":306,"stroke":257,"strokeWidth":301,"markerEnd":302},"152","194",[259,308],{"x":309,"y":310,"width":311,"height":310,"rx":312,"fill":313,"stroke":314,"strokeWidth":315},"306","52","210","10","#e6f0ea","#81b29a","1.8",[199,317,321],{"x":318,"y":319,"textAnchor":270,"fontSize":320,"fill":257},"411","83","11.5","3 tests request it directly",[259,323],{"x":309,"y":324,"width":311,"height":310,"rx":312,"fill":325,"stroke":257,"strokeWidth":326},"168","#f4f1de","1.6",[199,328,329],{"x":318,"y":278,"textAnchor":270,"fontSize":320,"fill":257},"billing_service fixture",[199,331,333],{"x":318,"y":332,"textAnchor":270,"fontSize":280,"fill":257},"208","→ 2 more tests",[72,335],{"x1":336,"y1":300,"x2":337,"y2":338,"stroke":257,"strokeWidth":301,"markerEnd":302},"520","596","118",[72,340],{"x1":336,"y1":306,"x2":337,"y2":341,"stroke":257,"strokeWidth":301,"markerEnd":302},"154",[259,343],{"x":344,"y":345,"width":346,"height":347,"rx":280,"fill":348,"stroke":349,"strokeWidth":283},"602","100","192","72","#fbe9e3","#e07a5f",[199,351,354],{"x":352,"y":353,"textAnchor":270,"fontSize":288,"fontWeight":272,"fill":257},"698","128","10 items collected",[199,356,358],{"x":352,"y":292,"textAnchor":270,"fontSize":280,"fill":357},"#8f3d22","5 tests × 2 params",[360,361,362,363,366],"figcaption",{},"The two tests reached through ",[14,364,365],{},"billing_service"," are the ones people forget: they never mention the repository, yet they run twice.",[27,368,370],{"id":369},"why-this-works","Why this works",[10,372,373,374,377,378,381],{},"During collection, pytest walks each test's fixture closure — every fixture it requests, and every fixture those request. When it finds a fixture with ",[14,375,376],{},"params",", it generates one test item per parameter, attaching the value as ",[14,379,380],{},"request.param"," and appending the parameter's id to the item's name. Because this happens over the whole closure, the variation propagates upward through any number of intermediate fixtures without those fixtures or tests declaring anything.",[10,383,384],{},"Scope is respected per parameter. A session-scoped parametrized fixture is instantiated once per parameter, and pytest reorders the collected items so that all tests using one parameter run together — which is why a session fixture with two expensive params is set up twice per run rather than once per test.",[27,386,388],{"id":387},"edge-cases-and-failure-modes","Edge cases and failure modes",[32,390,391,405,411,421,427],{},[35,392,393,397,398,400,401,404],{},[394,395,396],"strong",{},"Unreadable ids."," Without explicit ids, pytest uses the value if it is a simple type and ",[14,399,380],{}," index names such as ",[14,402,403],{},"invoice_repository0"," otherwise. Always pass ids.",[35,406,407,410],{},[394,408,409],{},"Accidental multiplication."," A parametrized fixture added deep in the graph doubles every test above it. Count collected items before and after the change.",[35,412,413,416,417,420],{},[394,414,415],{},"Parameters that must not combine."," Two parametrized fixtures in one closure produce the Cartesian product. Where some combinations are meaningless, mark them ",[14,418,419],{},"skip"," rather than letting them run and fail.",[35,422,423,426],{},[394,424,425],{},"One test that should see only one parameter."," Use indirect parametrization on that test to override the fixture's params, or depend on a narrower fixture.",[35,428,429,432],{},[394,430,431],{},"Session scope with expensive params."," Each parameter's setup runs once, but pytest's reordering can change test order in surprising ways. Do not depend on ordering across modules.",[27,434,436],{"id":435},"when-two-parametrized-fixtures-meet","When two parametrized fixtures meet",[10,438,439],{},"A single parametrized fixture multiplies by its parameter count. Two in the same closure multiply by each other, and the result is the Cartesian product — which is sometimes exactly the matrix you want and sometimes a set of combinations that make no sense together.",[10,441,442,443,446,447,450,451,453,454,446,457,450,460,463],{},"Consider a ",[14,444,445],{},"repository"," fixture with ",[14,448,449],{},"memory"," and ",[14,452,211],{},", and a ",[14,455,456],{},"serializer",[14,458,459],{},"json",[14,461,462],{},"msgpack",". A test requesting both runs four times, and all four combinations are meaningful: every storage backend should work with every wire format. That is the case fixture params were designed for, and the product is the whole point.",[10,465,466,467,446,470,450,473,476,477,480,481,484],{},"Now add a ",[14,468,469],{},"cache",[14,471,472],{},"none",[14,474,475],{},"redis",", where the Redis cache only makes sense alongside the SQL repository. The product is eight combinations, two of which pair an in-memory repository with a Redis cache — valid Python, meaningless architecture. Those two should not run. The clean expression is a mark that skips them with a reason, applied in a ",[14,478,479],{},"pytest_collection_modifyitems"," hook or a small fixture that inspects both parameters and calls ",[14,482,483],{},"pytest.skip",", so the report records the exclusion explicitly instead of silently producing tests that assert nothing useful.",[61,486,488],{"className":63,"code":487,"language":65,"meta":66,"style":66},"import pytest\n\n\n@pytest.fixture\ndef cache(request, invoice_repository):\n    backend = request.param\n    if backend == \"redis\" and isinstance(invoice_repository, InMemoryInvoiceRepository):\n        pytest.skip(\"redis cache is only deployed with the SQL repository\")\n    return make_cache(backend)\n",[14,489,490,494,498,502,507,512,517,522,527],{"__ignoreMap":66},[70,491,492],{"class":72,"line":73},[70,493,76],{},[70,495,496],{"class":72,"line":79},[70,497,83],{"emptyLinePlaceholder":82},[70,499,500],{"class":72,"line":86},[70,501,83],{"emptyLinePlaceholder":82},[70,503,504],{"class":72,"line":92},[70,505,506],{},"@pytest.fixture\n",[70,508,509],{"class":72,"line":97},[70,510,511],{},"def cache(request, invoice_repository):\n",[70,513,514],{"class":72,"line":102},[70,515,516],{},"    backend = request.param\n",[70,518,519],{"class":72,"line":108},[70,520,521],{},"    if backend == \"redis\" and isinstance(invoice_repository, InMemoryInvoiceRepository):\n",[70,523,524],{"class":72,"line":114},[70,525,526],{},"        pytest.skip(\"redis cache is only deployed with the SQL repository\")\n",[70,528,529],{"class":72,"line":120},[70,530,531],{},"    return make_cache(backend)\n",[10,533,534],{},"The skip appears in the summary with its reason, which is the property that matters: someone reading the report can see that the combination was considered and excluded, rather than wondering why it is missing.",[218,536,538,606],{"className":537},[221],[223,539,231,544,231,547,231,550,231,554,231,559,231,564,231,568,231,571,231,574,231,579,231,584,231,587,231,591,231,596,231,599,231,602,231,604],{"viewBox":540,"role":226,"ariaLabelledBy":541,"xmlns":230},"0 0 800 244",[542,543],"prod-t","prod-d",[233,545,546],{"id":542},"A Cartesian product with excluded combinations",[237,548,549],{"id":543},"A grid of repository parameters against cache parameters. Memory with no cache, SQL with no cache and SQL with Redis run. Memory with Redis is marked as skipped with a stated reason, so the report records that the combination was deliberately excluded.",[259,551],{"x":261,"y":261,"width":552,"height":553,"rx":264,"fill":265},"800","244",[199,555,558],{"x":556,"y":269,"textAnchor":270,"fontSize":557,"fontWeight":272,"fill":257},"400","15.5","The product, minus the combinations that never ship",[199,560,563],{"x":561,"y":562,"textAnchor":270,"fontSize":288,"fontWeight":272,"fill":257},"330","68","cache = none",[199,565,567],{"x":566,"y":562,"textAnchor":270,"fontSize":288,"fontWeight":272,"fill":257},"590","cache = redis",[199,569,570],{"x":298,"y":338,"textAnchor":270,"fontSize":288,"fontWeight":272,"fill":257},"repo = memory",[199,572,573],{"x":298,"y":278,"textAnchor":270,"fontSize":288,"fontWeight":272,"fill":257},"repo = sql",[259,575],{"x":311,"y":576,"width":577,"height":578,"rx":312,"fill":313,"stroke":314,"strokeWidth":283},"86","240","60",[199,580,583],{"x":561,"y":581,"textAnchor":270,"fontSize":320,"fill":582},"121","#2a5f49","runs",[259,585],{"x":586,"y":576,"width":577,"height":578,"rx":312,"fill":281,"stroke":282,"strokeWidth":283},"470",[199,588,590],{"x":566,"y":589,"textAnchor":270,"fontSize":320,"fontWeight":272,"fill":293},"112","skipped",[199,592,595],{"x":566,"y":593,"textAnchor":270,"fontSize":594,"fill":257},"132","10.5","\"only deployed with SQL\"",[259,597],{"x":311,"y":598,"width":577,"height":578,"rx":312,"fill":313,"stroke":314,"strokeWidth":283},"158",[199,600,583],{"x":561,"y":601,"textAnchor":270,"fontSize":320,"fill":582},"193",[259,603],{"x":586,"y":598,"width":577,"height":578,"rx":312,"fill":313,"stroke":314,"strokeWidth":283},[199,605,583],{"x":566,"y":601,"textAnchor":270,"fontSize":320,"fill":582},[360,607,608],{},"A skip with a reason documents the exclusion in every report; a silently absent combination leaves the next reader guessing whether it was forgotten.",[27,610,612],{"id":611},"choosing-fixture-params-over-the-alternatives","Choosing fixture params over the alternatives",[10,614,615],{},"Fixture params are one of three ways to run tests under several configurations, and the choice among them follows from where the variation lives.",[10,617,618,619,621],{},"When the variation belongs to one test — a set of inputs and expected outputs for a single function — ",[14,620,16],{}," on that test is clearest, because the cases sit next to the assertion that uses them. When the variation belongs to the environment and every test depending on some resource should see all of it, fixture params are clearest, because the list is declared once where the resource is built. And when the variation is expensive enough that it should not run on every push — a second database engine, a second interpreter — it belongs in the CI matrix rather than in pytest at all, with an environment variable selecting the configuration per job.",[10,623,624],{},"Mixing the three deliberately is normal. A suite might parametrize a parser test over twenty input strings, parametrize the repository fixture over two backends, and run the whole thing on three Python versions in CI. What goes wrong is using the wrong one: a CI matrix entry for something every developer should run locally, or a fixture param for something only one test cares about. Placing each variation at the level where it genuinely lives keeps the suite fast where it should be fast and thorough where it should be thorough. It also makes each variation easy to find: the reader looking for why a test runs twice has one obvious place to look. That discoverability is worth as much as the speed. A suite where every variation lives in the right layer is one where adding a new backend or format is a one-line change in one place.",[27,626,628],{"id":627},"keeping-the-multiplication-honest","Keeping the multiplication honest",[10,630,631],{},"The cost of a parametrized fixture is not visible in the diff that adds it. It shows up as a suite that is suddenly slower, and the change responsible looks innocuous.",[10,633,634],{},"A one-line check makes it visible at review time. Record the collected-item count on the main branch in CI, and fail or warn when a pull request changes it by more than a threshold without the change description explaining why. The collection is fast — no tests run — and the number is a surprisingly good proxy for suite cost.",[61,636,640],{"className":637,"code":638,"language":639,"meta":66,"style":66},"language-bash shiki shiki-themes github-light github-dark","pytest --collect-only -q | tail -1\n","bash",[14,641,642],{"__ignoreMap":66},[70,643,644,648,652,655,659,662],{"class":72,"line":73},[70,645,647],{"class":646},"sScJk","pytest",[70,649,651],{"class":650},"sj4cs"," --collect-only",[70,653,654],{"class":650}," -q",[70,656,658],{"class":657},"szBVR"," |",[70,660,661],{"class":646}," tail",[70,663,664],{"class":650}," -1\n",[61,666,669],{"className":667,"code":668,"language":199,"meta":66},[197],"1284 tests collected in 3.41s\n",[14,670,668],{"__ignoreMap":66},[10,672,673],{},"When the count jumps from 1284 to 2568, the pull request that parametrized a low-level fixture is identified immediately, and the conversation about whether every dependent test really needs both variants happens before the merge rather than after the pipeline doubles in length. Often the answer is that a handful of tests should use both backends and the rest should pin one, which indirect parametrization or a second, non-parametrized fixture expresses cleanly.",[218,675,677,743],{"className":676},[221],[223,678,231,683,231,686,231,689,231,692,231,695,231,700,231,705,231,710,231,713,231,716,231,719,231,722,231,726,231,729,231,735,231,739],{"viewBox":679,"role":226,"ariaLabelledBy":680,"xmlns":230},"0 0 800 234",[681,682],"cnt-t","cnt-d",[233,684,685],{"id":681},"Collected-item count as a guard against silent multiplication",[237,687,688],{"id":682},"A pull request adds params to a low-level fixture. The collected-item count doubles from 1284 to 2568. A CI step comparing the count against the main branch flags the change at review time, prompting a decision about which tests genuinely need both parameters.",[259,690],{"x":261,"y":261,"width":552,"height":691,"rx":264,"fill":265},"234",[199,693,694],{"x":556,"y":269,"textAnchor":270,"fontSize":557,"fontWeight":272,"fill":257},"Make the cost visible before merge",[259,696],{"x":697,"y":578,"width":698,"height":699,"rx":280,"fill":313,"stroke":314,"strokeWidth":283},"34","220","80",[199,701,704],{"x":702,"y":703,"textAnchor":270,"fontSize":288,"fontWeight":272,"fill":257},"144","90","main branch",[199,706,709],{"x":702,"y":707,"textAnchor":270,"fontSize":708,"fontWeight":272,"fill":582},"116","13","1,284 items",[259,711],{"x":712,"y":578,"width":698,"height":699,"rx":280,"fill":348,"stroke":349,"strokeWidth":283},"290",[199,714,715],{"x":556,"y":703,"textAnchor":270,"fontSize":288,"fontWeight":272,"fill":257},"pull request",[199,717,718],{"x":556,"y":707,"textAnchor":270,"fontSize":708,"fontWeight":272,"fill":357},"2,568 items",[259,720],{"x":721,"y":578,"width":698,"height":699,"rx":280,"fill":281,"stroke":282,"strokeWidth":283},"546",[199,723,725],{"x":724,"y":703,"textAnchor":270,"fontSize":288,"fontWeight":272,"fill":257},"656","CI check",[199,727,728],{"x":724,"y":707,"textAnchor":270,"fontSize":280,"fill":293},"+100% — explain or narrow",[259,730],{"x":697,"y":731,"width":732,"height":733,"rx":312,"fill":265,"stroke":734,"strokeWidth":301},"164","732","50","rgba(61,64,91,0.35)",[199,736,738],{"x":556,"y":737,"textAnchor":270,"fontSize":320,"fill":257},"186","Collection runs no tests, so the check costs seconds — and it catches the one-line",[199,740,742],{"x":556,"y":741,"textAnchor":270,"fontSize":320,"fill":257},"204","change that would otherwise double the pipeline without anyone noticing why.",[360,744,745],{},"The number is crude, but a doubling is never an accident worth merging unexamined.",[27,747,749],{"id":748},"frequently-asked-questions","Frequently Asked Questions",[10,751,752,755,758],{},[394,753,754],{},"What is the difference between fixture params and @pytest.mark.parametrize?",[14,756,757],{},"parametrize"," varies a test's arguments and applies to one test. Fixture params vary a fixture, and every test that requests that fixture — directly or through another fixture — is run once per parameter. Use fixture params when the variation is a property of the environment rather than of one test.",[10,760,761,764,765,768,769,771,772,775],{},[394,762,763],{},"How do I mark one parameter as xfail or skip?","\nWrap it in ",[14,766,767],{},"pytest.param"," with marks, exactly as with ",[14,770,757],{},": ",[14,773,774],{},"params=[\"sqlite\", pytest.param(\"mysql\", marks=pytest.mark.xfail(reason=\"…\"))]",". The mark applies to every test instance that receives that parameter.",[10,777,778,781],{},[394,779,780],{},"How does scope interact with params?","\nThe fixture is set up once per parameter per scope instance. A session-scoped fixture with three params is created three times per session, and pytest reorders tests to group those sharing a parameter so each is set up only once.",[27,783,785],{"id":784},"related","Related",[32,787,788,794,801,807],{},[35,789,790,793],{},[45,791,792],{"href":47},"Mastering pytest Fixtures"," — the dependency graph params propagate through.",[35,795,796,800],{},[45,797,799],{"href":798},"\u002Fadvanced-pytest-architecture-configuration\u002Fadvanced-parametrization-techniques\u002Findirect-parametrization-with-fixtures\u002F","Indirect Parametrization with Fixtures"," — overriding a fixture's params for one test.",[35,802,803,806],{},[45,804,805],{"href":54},"Generating Readable Test IDs"," — why explicit ids matter for history and reports.",[35,808,809,813],{},[45,810,812],{"href":811},"\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"," — the fake half of the memory\u002Fsql pair above.",[10,815,816,817],{},"← Back to ",[45,818,792],{"href":47},[820,821,822],"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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":66,"searchDepth":79,"depth":79,"links":824},[825,826,827,828,829,830,831,832,833],{"id":29,"depth":79,"text":30},{"id":58,"depth":79,"text":59},{"id":369,"depth":79,"text":370},{"id":387,"depth":79,"text":388},{"id":435,"depth":79,"text":436},{"id":611,"depth":79,"text":612},{"id":627,"depth":79,"text":628},{"id":748,"depth":79,"text":749},{"id":784,"depth":79,"text":785},"Run every dependent test once per backend or configuration with fixture params: readable ids, per-parameter marks, scope interaction, and the multiplication to watch for.","md",{"slug":837,"type":838,"breadcrumb":839,"datePublished":840,"dateModified":840,"faq":841,"howto":848},"parametrizing-fixtures-with-params-and-ids","article","Fixture params","2026-09-18",[842,844,846],{"q":754,"a":843},"parametrize varies a test's arguments and applies to one test. Fixture params vary a fixture, and every test that requests that fixture — directly or through another fixture — is run once per parameter. Use fixture params when the variation is a property of the environment rather than of one test.",{"q":763,"a":845},"Wrap it in pytest.param with marks, exactly as with parametrize: params=['sqlite', pytest.param('mysql', marks=pytest.mark.xfail(reason='…'))]. The mark applies to every test instance that receives that parameter.",{"q":780,"a":847},"The fixture is set up once per parameter per scope instance. A session-scoped fixture with three params is created three times per session, and pytest reorders tests to group those sharing a parameter so each is set up only once.",{"name":849,"description":850,"steps":851},"How to parametrize a fixture readably","Declare params with explicit ids, mark individual parameters, and keep an eye on the multiplication across dependent tests.",[852,855,858,861,864],{"name":853,"text":854},"Declare params on the fixture","Pass params to @pytest.fixture and read the current value from request.param.",{"name":856,"text":857},"Give every parameter an explicit id","Use ids= or pytest.param(..., id=...) so test names are readable and stable.",{"name":859,"text":860},"Mark individual parameters","Wrap known-bad combinations in pytest.param with xfail or skip marks.",{"name":862,"text":863},"Check the multiplication","Count collected items with --collect-only before and after, since every dependent test multiplies.",{"name":865,"text":866},"Narrow where needed","Use indirect parametrization or a separate fixture for tests that should see only one parameter.","\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Fparametrizing-fixtures-with-params-and-ids",{"title":5,"description":834},"advanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Fparametrizing-fixtures-with-params-and-ids\u002Findex","Vw_vI0s1o_L4cO3Dmqk94il23bTXxQdhTLKirv6CkSM",1789718767397]