[{"data":1,"prerenderedAt":1028},["ShallowReactive",2],{"page-\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002Fproperty-testing-django-models-with-hypothesis\u002F":3},{"id":4,"title":5,"body":6,"description":991,"extension":992,"meta":993,"navigation":88,"path":1024,"seo":1025,"stem":1026,"__hash__":1027},"content\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002Fproperty-testing-django-models-with-hypothesis\u002Findex.md","Property-Testing Django Models with Hypothesis",{"type":7,"value":8,"toc":980},"minimark",[9,22,25,30,56,60,284,439,443,464,470,474,527,531,534,547,553,559,575,648,652,655,661,674,681,685,691,703,724,790,796,882,886,900,925,938,942,971,976],[10,11,12,13,17,18,21],"p",{},"Django models encode constraints in field definitions — lengths, nullability, choices, validators — and business rules in methods and ",[14,15,16],"code",{},"clean()",". Example-based tests exercise both with a handful of hand-written instances, usually the same three or four values everyone copies between tests. ",[14,19,20],{},"hypothesis.extra.django"," generates instances from the model definition itself, respecting every declared constraint, and runs each example in its own transaction so the database stays clean between them.",[10,23,24],{},"That combination is unusually productive. The constraints come for free from the model, the isolation comes for free from the test case, and the properties worth asserting — that saved instances validate, that serializers round-trip, that derived fields agree with their inputs — are short and general. Bugs found this way tend to be the ones hand-written fixtures never reach: a name with an apostrophe, an empty optional field, a boundary length. They also tend to be the bugs users report first, because real data is far more varied than any fixture file: customers type apostrophes, leave optional fields empty and hit length limits constantly, and a model layer that has only ever been tested with 'Jane Smith' is untested against most of the input it will actually receive.",[26,27,29],"h2",{"id":28},"prerequisites","Prerequisites",[31,32,33,40,47],"ul",{},[34,35,36,39],"li",{},[14,37,38],{},"hypothesis[django] >= 6.100"," and a supported Django version.",[34,41,42,43,46],{},"A test database configured as usual; ",[14,44,45],{},"pytest-django"," works alongside.",[34,48,49,50,55],{},"The fixture-isolation rules from ",[51,52,54],"a",{"href":53},"\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002Fcombining-given-with-pytest-fixtures-safely\u002F","combining @given with pytest fixtures safely",".",[26,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","from hypothesis import given, strategies as st\nfrom hypothesis.extra.django import TestCase, from_model\n\nfrom shop.models import Customer, Order\n\n\ncustomers = from_model(\n    Customer,\n    email=st.emails(),                                  # narrower than the CharField\n    country=st.sampled_from([\"GB\", \"DE\", \"FR\", \"US\"]),\n)\norders = from_model(\n    Order,\n    customer=customers,                                 # required foreign key\n    total_minor=st.integers(min_value=0, max_value=10_000_000),\n)\n\n\nclass CustomerProperties(TestCase):\n    \"\"\"Each example runs in its own atomic block, rolled back afterwards.\"\"\"\n\n    @given(customers)\n    def test_saved_customers_pass_full_clean(self, customer):\n        customer.full_clean()                           # raises if invalid\n\n    @given(customers)\n    def test_display_name_is_never_empty(self, customer):\n        assert customer.display_name().strip()\n\n\nclass OrderProperties(TestCase):\n    @given(orders)\n    def test_serializer_round_trips(self, order):\n        data = OrderSerializer(order).data\n        restored = OrderSerializer(data=data)\n        assert restored.is_valid(), restored.errors\n        assert restored.validated_data[\"total_minor\"] == order.total_minor\n","python","",[14,68,69,77,83,90,96,101,106,112,118,124,130,136,142,148,154,160,165,170,175,181,187,192,198,204,210,215,220,226,232,237,242,248,254,260,266,272,278],{"__ignoreMap":66},[70,71,74],"span",{"class":72,"line":73},"line",1,[70,75,76],{},"from hypothesis import given, strategies as st\n",[70,78,80],{"class":72,"line":79},2,[70,81,82],{},"from hypothesis.extra.django import TestCase, from_model\n",[70,84,86],{"class":72,"line":85},3,[70,87,89],{"emptyLinePlaceholder":88},true,"\n",[70,91,93],{"class":72,"line":92},4,[70,94,95],{},"from shop.models import Customer, Order\n",[70,97,99],{"class":72,"line":98},5,[70,100,89],{"emptyLinePlaceholder":88},[70,102,104],{"class":72,"line":103},6,[70,105,89],{"emptyLinePlaceholder":88},[70,107,109],{"class":72,"line":108},7,[70,110,111],{},"customers = from_model(\n",[70,113,115],{"class":72,"line":114},8,[70,116,117],{},"    Customer,\n",[70,119,121],{"class":72,"line":120},9,[70,122,123],{},"    email=st.emails(),                                  # narrower than the CharField\n",[70,125,127],{"class":72,"line":126},10,[70,128,129],{},"    country=st.sampled_from([\"GB\", \"DE\", \"FR\", \"US\"]),\n",[70,131,133],{"class":72,"line":132},11,[70,134,135],{},")\n",[70,137,139],{"class":72,"line":138},12,[70,140,141],{},"orders = from_model(\n",[70,143,145],{"class":72,"line":144},13,[70,146,147],{},"    Order,\n",[70,149,151],{"class":72,"line":150},14,[70,152,153],{},"    customer=customers,                                 # required foreign key\n",[70,155,157],{"class":72,"line":156},15,[70,158,159],{},"    total_minor=st.integers(min_value=0, max_value=10_000_000),\n",[70,161,163],{"class":72,"line":162},16,[70,164,135],{},[70,166,168],{"class":72,"line":167},17,[70,169,89],{"emptyLinePlaceholder":88},[70,171,173],{"class":72,"line":172},18,[70,174,89],{"emptyLinePlaceholder":88},[70,176,178],{"class":72,"line":177},19,[70,179,180],{},"class CustomerProperties(TestCase):\n",[70,182,184],{"class":72,"line":183},20,[70,185,186],{},"    \"\"\"Each example runs in its own atomic block, rolled back afterwards.\"\"\"\n",[70,188,190],{"class":72,"line":189},21,[70,191,89],{"emptyLinePlaceholder":88},[70,193,195],{"class":72,"line":194},22,[70,196,197],{},"    @given(customers)\n",[70,199,201],{"class":72,"line":200},23,[70,202,203],{},"    def test_saved_customers_pass_full_clean(self, customer):\n",[70,205,207],{"class":72,"line":206},24,[70,208,209],{},"        customer.full_clean()                           # raises if invalid\n",[70,211,213],{"class":72,"line":212},25,[70,214,89],{"emptyLinePlaceholder":88},[70,216,218],{"class":72,"line":217},26,[70,219,197],{},[70,221,223],{"class":72,"line":222},27,[70,224,225],{},"    def test_display_name_is_never_empty(self, customer):\n",[70,227,229],{"class":72,"line":228},28,[70,230,231],{},"        assert customer.display_name().strip()\n",[70,233,235],{"class":72,"line":234},29,[70,236,89],{"emptyLinePlaceholder":88},[70,238,240],{"class":72,"line":239},30,[70,241,89],{"emptyLinePlaceholder":88},[70,243,245],{"class":72,"line":244},31,[70,246,247],{},"class OrderProperties(TestCase):\n",[70,249,251],{"class":72,"line":250},32,[70,252,253],{},"    @given(orders)\n",[70,255,257],{"class":72,"line":256},33,[70,258,259],{},"    def test_serializer_round_trips(self, order):\n",[70,261,263],{"class":72,"line":262},34,[70,264,265],{},"        data = OrderSerializer(order).data\n",[70,267,269],{"class":72,"line":268},35,[70,270,271],{},"        restored = OrderSerializer(data=data)\n",[70,273,275],{"class":72,"line":274},36,[70,276,277],{},"        assert restored.is_valid(), restored.errors\n",[70,279,281],{"class":72,"line":280},37,[70,282,283],{},"        assert restored.validated_data[\"total_minor\"] == order.total_minor\n",[285,286,289,435],"figure",{"className":287},[288],"diagram",[290,291,298,299,298,303,298,307,298,325,298,333,298,342,298,351,298,357,298,362,298,366,298,370,298,376,298,383,298,387,298,391,298,394,298,398,298,402,298,408,298,412,298,416,298,419,298,423,298,430],"svg",{"viewBox":292,"role":293,"ariaLabelledBy":294,"xmlns":297},"0 0 820 262","img",[295,296],"dj-t","dj-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[300,301,302],"title",{"id":295},"From model definition to generated, isolated examples",[304,305,306],"desc",{"id":296},"from_model reads the model's field definitions — max length, nullability, choices and validators — and combines them with any explicit overrides to produce a strategy. Hypothesis's Django TestCase runs each generated example inside its own atomic block and rolls it back, so every example starts from the same empty database.",[308,309,310,311,298],"defs",{},"\n    ",[312,313,320],"marker",{"id":314,"viewBox":315,"refX":316,"refY":317,"markerWidth":318,"markerHeight":318,"orient":319},"dj-a","0 0 10 10","9","5","7","auto-start-reverse",[321,322],"path",{"d":323,"fill":324},"M0 0 L10 5 L0 10 z","#3d405b",[326,327],"rect",{"x":328,"y":328,"width":329,"height":330,"rx":331,"fill":332},"0","820","262","14","#fffdf8",[334,335,341],"text",{"x":336,"y":337,"textAnchor":338,"fontSize":339,"fontWeight":340,"fill":324},"410","28","middle","16","700","Constraints from the model, isolation from the test case",[326,343],{"x":344,"y":345,"width":346,"height":347,"rx":348,"fill":349,"stroke":324,"strokeWidth":350},"26","60","210","130","11","#f4f1de","1.6",[334,352,356],{"x":353,"y":354,"textAnchor":338,"fontSize":355,"fontWeight":340,"fill":324},"131","86","12","model fields",[334,358,361],{"x":359,"y":360,"fontSize":348,"fill":324},"42","112","max_length, null, blank",[334,363,365],{"x":359,"y":364,"fontSize":348,"fill":324},"134","choices, validators",[334,367,369],{"x":359,"y":368,"fontSize":348,"fill":324},"156","field types",[72,371],{"x1":372,"y1":373,"x2":374,"y2":373,"stroke":324,"strokeWidth":350,"markerEnd":375},"240","125","280","url(#dj-a)",[326,377],{"x":378,"y":345,"width":379,"height":347,"rx":348,"fill":380,"stroke":381,"strokeWidth":382},"286","220","#f7f0da","#f2cc8f","2",[334,384,386],{"x":385,"y":354,"textAnchor":338,"fontSize":355,"fontWeight":340,"fill":324},"396","from_model(…)",[334,388,390],{"x":389,"y":360,"fontSize":348,"fill":324},"302","+ domain overrides",[334,392,393],{"x":389,"y":364,"fontSize":348,"fill":324},"+ related strategies",[334,395,397],{"x":389,"y":368,"fontSize":348,"fill":396},"#8a5a00","valid by construction",[72,399],{"x1":400,"y1":373,"x2":401,"y2":373,"stroke":324,"strokeWidth":350,"markerEnd":375},"510","550",[326,403],{"x":404,"y":345,"width":405,"height":347,"rx":348,"fill":406,"stroke":407,"strokeWidth":382},"556","238","#e6f0ea","#81b29a",[334,409,411],{"x":410,"y":354,"textAnchor":338,"fontSize":355,"fontWeight":340,"fill":324},"675","per-example atomic",[334,413,415],{"x":414,"y":360,"fontSize":348,"fill":324},"572","example saved, tested,",[334,417,418],{"x":414,"y":364,"fontSize":348,"fill":324},"rolled back",[334,420,422],{"x":414,"y":368,"fontSize":348,"fill":421},"#2a5f49","next example starts clean",[326,424],{"x":344,"y":425,"width":426,"height":427,"rx":316,"fill":332,"stroke":428,"strokeWidth":429},"208","768","36","rgba(61,64,91,0.35)","1.4",[334,431,434],{"x":336,"y":432,"textAnchor":338,"fontSize":433,"fill":324},"231","11.5","Using django.test.TestCase instead shares one transaction across every example.",[436,437,438],"figcaption",{},"The model definition is the specification the strategy is derived from, so tightening a field in the model tightens the generated data automatically.",[26,440,442],{"id":441},"why-this-works","Why this works",[10,444,445,448,449,452,453,456,457,460,461,55],{},[14,446,447],{},"from_model"," builds a strategy per field from the field's own definition — a ",[14,450,451],{},"CharField(max_length=20)"," becomes text of at most twenty characters, a field with ",[14,454,455],{},"choices"," becomes a sample from those choices, a nullable field sometimes generates ",[14,458,459],{},"None"," — and saves the resulting instance, so the object the test receives is a real row with a real primary key. Keyword arguments override individual fields where the domain is narrower than the column type, such as an email address stored in a plain ",[14,462,463],{},"CharField",[10,465,466,469],{},[14,467,468],{},"hypothesis.extra.django.TestCase"," wraps each example, not each test method, in a transaction that is rolled back afterwards. That is the isolation plain Django test cases cannot give a property test, because their transaction spans the whole method — every example in it would see rows created by the previous ones, exactly the leak the function-scoped-fixture health check warns about.",[26,471,473],{"id":472},"edge-cases-and-failure-modes","Edge cases and failure modes",[31,475,476,486,492,504,514],{},[34,477,478,485],{},[479,480,481,482,55],"strong",{},"Using ",[14,483,484],{},"django.test.TestCase"," Examples share one transaction and see each other's rows. Use the Hypothesis subclass.",[34,487,488,491],{},[479,489,490],{},"Unique fields colliding."," A unique field with a small value space can collide across generated instances within one example. Widen the strategy or generate deterministic unique values.",[34,493,494,497,498,500,501,55],{},[479,495,496],{},"Custom field types."," Fields ",[14,499,447],{}," does not recognise need an explicit strategy passed as a keyword, or registered once with ",[14,502,503],{},"register_field_strategy",[34,505,506,509,510,513],{},[479,507,508],{},"Slow examples."," Every example writes to the database. Keep ",[14,511,512],{},"max_examples"," modest for model tests and disable the per-example deadline where queries vary in latency.",[34,515,516,522,523,526],{},[479,517,518,521],{},[14,519,520],{},"TransactionTestCase"," needs."," Code that must see committed data from another connection cannot run inside the rollback. Use ",[14,524,525],{},"hypothesis.extra.django.TransactionTestCase"," and accept the slower truncation.",[26,528,530],{"id":529},"properties-worth-asserting-about-models","Properties worth asserting about models",[10,532,533],{},"Model tests often stall at \"what would I even check?\", because the obvious assertions are about specific values. A handful of general properties apply to nearly every model and find real bugs.",[10,535,536,539,540,543,544,546],{},[479,537,538],{},"Saved instances are valid."," Anything that can be saved should pass ",[14,541,542],{},"full_clean()",". A failure means the database accepts values the application's own validation rejects — a missing validator, a ",[14,545,16],{}," method assuming a field is set, a constraint enforced in one place and not the other.",[10,548,549,552],{},[479,550,551],{},"Serialisation round-trips."," Serialise with the API serializer, deserialize the result, and the data must validate and match. Failures here are among the most common and most user-visible: a decimal rendered with the wrong precision, a timezone dropped, a nullable field rejected on the way back in.",[10,554,555,558],{},[479,556,557],{},"Derived values agree with their inputs."," Totals equal the sum of their lines, status fields agree with timestamps, slugs are derived from names. Generating many combinations of inputs catches the rounding and ordering errors in those derivations.",[10,560,561,564,565,568,569,572,573,55],{},[479,562,563],{},"Display methods never fail."," ",[14,566,567],{},"__str__",", ",[14,570,571],{},"display_name",", admin list columns — methods that should handle every valid instance, including those with empty optional fields. They are rarely tested and frequently raise on ",[14,574,459],{},[285,576,578,645],{"className":577},[288],[290,579,298,584,298,587,298,590,298,594,298,599,298,605,298,610,298,614,298,617,298,621,298,624,298,629,298,633,298,637,298,639,298,642],{"viewBox":580,"role":293,"ariaLabelledBy":581,"xmlns":297},"0 0 800 236",[582,583],"mp2-t","mp2-d",[300,585,586],{"id":582},"Four general properties for Django models",[304,588,589],{"id":583},"Four cards. Saved instances pass full_clean. Serializer output round-trips and validates. Derived fields agree with their inputs. Display methods succeed for every valid instance including empty optional fields. Each is a short assertion checked against every generated instance.",[326,591],{"x":328,"y":328,"width":592,"height":593,"rx":331,"fill":332},"800","236",[334,595,598],{"x":596,"y":337,"textAnchor":338,"fontSize":597,"fontWeight":340,"fill":324},"400","15.5","Checks that apply to almost any model",[326,600],{"x":601,"y":602,"width":603,"height":604,"rx":348,"fill":406,"stroke":407,"strokeWidth":382},"24","50","370","78",[334,606,609],{"x":607,"y":608,"fontSize":355,"fontWeight":340,"fill":324},"44","76","saved ⇒ valid",[334,611,613],{"x":607,"y":612,"fontSize":348,"fill":324},"100","instance.full_clean() never raises",[326,615],{"x":616,"y":602,"width":603,"height":604,"rx":348,"fill":380,"stroke":381,"strokeWidth":382},"406",[334,618,620],{"x":619,"y":608,"fontSize":355,"fontWeight":340,"fill":324},"426","serializer round trip",[334,622,623],{"x":619,"y":612,"fontSize":348,"fill":324},"out and back in, still valid, same values",[326,625],{"x":601,"y":626,"width":603,"height":604,"rx":348,"fill":627,"stroke":628,"strokeWidth":382},"138","#fbe9e3","#e07a5f",[334,630,632],{"x":607,"y":631,"fontSize":355,"fontWeight":340,"fill":324},"164","derived fields agree",[334,634,636],{"x":607,"y":635,"fontSize":348,"fill":324},"188","total == sum(lines), slug from name",[326,638],{"x":616,"y":626,"width":603,"height":604,"rx":348,"fill":349,"stroke":324,"strokeWidth":350},[334,640,641],{"x":619,"y":631,"fontSize":355,"fontWeight":340,"fill":324},"display never fails",[334,643,644],{"x":619,"y":635,"fontSize":348,"fill":324},"__str__ and admin columns on empty fields",[436,646,647],{},"Four short tests per model, each checked against hundreds of generated instances, cover more than the usual pile of hand-picked fixtures.",[26,649,651],{"id":650},"keeping-model-properties-fast","Keeping model properties fast",[10,653,654],{},"Every generated example writes at least one row, and related-object strategies write several, so model properties are among the slowest Hypothesis tests a project will have. The budget needs managing deliberately rather than left at the default.",[10,656,657,658,660],{},"The first lever is ",[14,659,512],{},". Fifty examples per model property is usually enough to surface the field-level bugs these tests target — empty strings, boundary lengths, missing optional values — because those cases are generated early and often. A nightly profile can raise the budget for deeper search without slowing every pull request.",[10,662,663,664,666,667,670,671,673],{},"The second is avoiding persistence where it is not needed. A property about a display method or a pure computation does not need a saved row; ",[14,665,447],{}," always saves, but ",[14,668,669],{},"st.builds(Model, ...)"," with field strategies constructs an unsaved instance in memory, which is an order of magnitude faster. Reserve ",[14,672,447],{}," for properties that genuinely involve the database — uniqueness, foreign keys, query behaviour — and use unsaved instances for everything else.",[10,675,676,677,680],{},"The third is disabling the per-example deadline. Database writes vary in latency, and the default deadline turns an occasional slow insert into a spurious failure. ",[14,678,679],{},"@settings(deadline=None)"," on model test classes removes that noise without weakening the properties themselves. Together the three changes usually bring a model test class from tens of seconds to a few.",[26,682,684],{"id":683},"registering-strategies-once-for-the-whole-project","Registering strategies once for the whole project",[10,686,687,688,690],{},"Passing the same overrides to ",[14,689,447],{}," in every test module duplicates the domain rules and lets them drift. Hypothesis offers two registration points that make a model's strategy a single, shared definition.",[10,692,693,695,696,699,700,702],{},[14,694,503],{}," teaches Hypothesis how to generate values for a custom field type — a ",[14,697,698],{},"MoneyField",", an encrypted field, a field backed by a third-party type — once, for the whole project. After registration, every ",[14,701,447],{}," call handles that field automatically, and nothing in the tests needs to know it is special.",[10,704,705,706,709,710,568,713,716,717,720,721,723],{},"A module of named model strategies does the same for domain constraints. ",[14,707,708],{},"shop\u002Ftesting\u002Fstrategies.py"," exports ",[14,711,712],{},"customers",[14,714,715],{},"orders"," and ",[14,718,719],{},"paid_orders",", each built from ",[14,722,447],{}," with the right overrides, and every test imports them. When the domain changes — a new country is supported, the maximum order total is raised — one strategy changes and every property that uses it follows.",[61,725,727],{"className":63,"code":726,"language":65,"meta":66,"style":66},"# shop\u002Ftesting\u002Fstrategies.py\nfrom hypothesis import strategies as st\nfrom hypothesis.extra.django import from_model, register_field_strategy\n\nfrom shop.fields import MoneyField\nfrom shop.models import Customer, Order\n\nregister_field_strategy(MoneyField, st.integers(min_value=0, max_value=10_000_000))\n\ncustomers = from_model(Customer, email=st.emails(),\n                       country=st.sampled_from([\"GB\", \"DE\", \"FR\", \"US\"]))\norders = from_model(Order, customer=customers)\npaid_orders = from_model(Order, customer=customers, status=st.just(\"paid\"))\n",[14,728,729,734,739,744,748,753,757,761,766,770,775,780,785],{"__ignoreMap":66},[70,730,731],{"class":72,"line":73},[70,732,733],{},"# shop\u002Ftesting\u002Fstrategies.py\n",[70,735,736],{"class":72,"line":79},[70,737,738],{},"from hypothesis import strategies as st\n",[70,740,741],{"class":72,"line":85},[70,742,743],{},"from hypothesis.extra.django import from_model, register_field_strategy\n",[70,745,746],{"class":72,"line":92},[70,747,89],{"emptyLinePlaceholder":88},[70,749,750],{"class":72,"line":98},[70,751,752],{},"from shop.fields import MoneyField\n",[70,754,755],{"class":72,"line":103},[70,756,95],{},[70,758,759],{"class":72,"line":108},[70,760,89],{"emptyLinePlaceholder":88},[70,762,763],{"class":72,"line":114},[70,764,765],{},"register_field_strategy(MoneyField, st.integers(min_value=0, max_value=10_000_000))\n",[70,767,768],{"class":72,"line":120},[70,769,89],{"emptyLinePlaceholder":88},[70,771,772],{"class":72,"line":126},[70,773,774],{},"customers = from_model(Customer, email=st.emails(),\n",[70,776,777],{"class":72,"line":132},[70,778,779],{},"                       country=st.sampled_from([\"GB\", \"DE\", \"FR\", \"US\"]))\n",[70,781,782],{"class":72,"line":138},[70,783,784],{},"orders = from_model(Order, customer=customers)\n",[70,786,787],{"class":72,"line":144},[70,788,789],{},"paid_orders = from_model(Order, customer=customers, status=st.just(\"paid\"))\n",[10,791,792,793,795],{},"The module is the same idea as a factory module for example-based tests, and it belongs next to the models for the same reason: a change to a model's constraints and a change to its strategy should be reviewed together. A test that checks every generated instance passes ",[14,794,542],{}," keeps them honest, failing the day a model gains a constraint its strategy does not respect.",[285,797,799,879],{"className":798},[288],[290,800,298,805,298,808,298,811,298,818,298,821,298,824,298,829,298,832,298,836,298,840,298,842,298,848,298,850,298,854,298,860,298,865,298,868,298,872,298,875],{"viewBox":801,"role":293,"ariaLabelledBy":802,"xmlns":297},"0 0 800 226",[803,804],"reg-t","reg-d",[300,806,807],{"id":803},"Shared strategies for a Django project",[304,809,810],{"id":804},"A single strategies module registers custom field strategies once and exports named model strategies with domain overrides. Every test module imports from it, so a change to a domain rule is made in one place and all properties that depend on it follow.",[308,812,310,813,298],{},[312,814,816],{"id":815,"viewBox":315,"refX":316,"refY":317,"markerWidth":318,"markerHeight":318,"orient":319},"reg-a",[321,817],{"d":323,"fill":407},[326,819],{"x":328,"y":328,"width":592,"height":820,"rx":331,"fill":332},"226",[334,822,823],{"x":596,"y":337,"textAnchor":338,"fontSize":597,"fontWeight":340,"fill":324},"One definition of valid data per model",[326,825],{"x":344,"y":826,"width":827,"height":828,"rx":348,"fill":380,"stroke":381,"strokeWidth":382},"70","260","110",[334,830,708],{"x":368,"y":831,"textAnchor":338,"fontSize":355,"fontWeight":340,"fill":324},"96",[334,833,835],{"x":359,"y":834,"fontSize":348,"fill":324},"122","register_field_strategy(…)",[334,837,839],{"x":359,"y":838,"fontSize":348,"fill":324},"144","customers, orders,",[334,841,719],{"x":359,"y":631,"fontSize":348,"fill":324},[72,843],{"x1":844,"y1":612,"x2":845,"y2":826,"stroke":407,"strokeWidth":846,"markerEnd":847},"290","360","1.8","url(#reg-a)",[72,849],{"x1":844,"y1":373,"x2":845,"y2":373,"stroke":407,"strokeWidth":846,"markerEnd":847},[72,851],{"x1":844,"y1":852,"x2":845,"y2":853,"stroke":407,"strokeWidth":846,"markerEnd":847},"150","180",[326,855],{"x":856,"y":857,"width":858,"height":859,"rx":316,"fill":406,"stroke":407,"strokeWidth":350},"366","48","408","40",[334,861,864],{"x":862,"y":863,"textAnchor":338,"fontSize":433,"fill":324},"570","73","test_customers.py",[326,866],{"x":856,"y":867,"width":858,"height":859,"rx":316,"fill":406,"stroke":407,"strokeWidth":350},"104",[334,869,871],{"x":862,"y":870,"textAnchor":338,"fontSize":433,"fill":324},"129","test_orders.py",[326,873],{"x":856,"y":874,"width":858,"height":859,"rx":316,"fill":406,"stroke":407,"strokeWidth":350},"160",[334,876,878],{"x":862,"y":877,"textAnchor":338,"fontSize":433,"fill":324},"185","test_serializers.py",[436,880,881],{},"A new supported country is a one-line change in the module, and every property picks it up on its next run.",[26,883,885],{"id":884},"frequently-asked-questions","Frequently Asked Questions",[10,887,888,891,893,894,896,897,899],{},[479,889,890],{},"Which TestCase should Hypothesis tests use in Django?",[14,892,468],{},", not ",[14,895,484],{},". It wraps each generated example in its own atomic block and rolls it back, so rows created by one example never appear in the next. With plain ",[14,898,484],{}," the transaction spans the whole test method and examples leak into each other.",[10,901,902,905,906,568,909,568,912,568,915,917,918,920,921,924],{},[479,903,904],{},"How does from_model know what values are valid?","\nIt inspects each field: ",[14,907,908],{},"max_length",[14,910,911],{},"null",[14,913,914],{},"blank",[14,916,455],{},", validators, and the field type. Generated instances satisfy those constraints and are saved to the database, so a ",[14,919,463],{}," with ",[14,922,923],{},"max_length=20"," never receives 21 characters.",[10,926,927,930,931,933,934,937],{},[479,928,929],{},"How do I generate a model with a required foreign key?","\nPass a strategy for the related field explicitly, usually ",[14,932,447],{}," of the related model: ",[14,935,936],{},"from_model(Order, customer=from_model(Customer))",". Hypothesis creates the related row first and links it.",[26,939,941],{"id":940},"related","Related",[31,943,944,951,957,964],{},[34,945,946,950],{},[51,947,949],{"href":948},"\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002F","Hypothesis Integration with pytest & Frameworks"," — profiles and isolation across frameworks.",[34,952,953,956],{},[51,954,955],{"href":53},"Combining @given with pytest Fixtures Safely"," — the isolation problem the Django TestCase solves.",[34,958,959,963],{},[51,960,962],{"href":961},"\u002Fproperty-based-fuzz-testing-strategies\u002Fadvanced-property-based-testing\u002Fround-trip-properties-for-serializers-and-parsers\u002F","Round-Trip Properties for Serializers and Parsers"," — the serializer property in depth.",[34,965,966,970],{},[51,967,969],{"href":968},"\u002Fintegration-database-and-service-testing\u002Fdatabase-fixtures-and-transactional-tests\u002F","Database Fixtures & Transactional Tests"," — the same rollback idea outside Django.",[10,972,973,974],{},"← Back to ",[51,975,949],{"href":948},[977,978,979],"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":66,"searchDepth":79,"depth":79,"links":981},[982,983,984,985,986,987,988,989,990],{"id":28,"depth":79,"text":29},{"id":58,"depth":79,"text":59},{"id":441,"depth":79,"text":442},{"id":472,"depth":79,"text":473},{"id":529,"depth":79,"text":530},{"id":650,"depth":79,"text":651},{"id":683,"depth":79,"text":684},{"id":884,"depth":79,"text":885},{"id":940,"depth":79,"text":941},"Generate valid Django model instances with hypothesis.extra.django: from_model, field overrides, per-example transactions, related objects, and properties worth asserting.","md",{"slug":994,"type":995,"breadcrumb":996,"datePublished":997,"dateModified":997,"faq":998,"howto":1005},"property-testing-django-models-with-hypothesis","article","Django + Hypothesis","2026-09-18",[999,1001,1003],{"q":890,"a":1000},"hypothesis.extra.django.TestCase, not django.test.TestCase. It wraps each generated example in its own atomic block and rolls it back, so rows created by one example never appear in the next. With plain django.test.TestCase the transaction spans the whole test method and examples leak into each other.",{"q":904,"a":1002},"It inspects each field: max_length, null, blank, choices, validators, and the field type. Generated instances satisfy those constraints and are saved to the database, so a CharField with max_length=20 never receives 21 characters.",{"q":929,"a":1004},"Pass a strategy for the related field explicitly, usually from_model of the related model: from_model(Order, customer=from_model(Customer)). Hypothesis creates the related row first and links it.",{"name":1006,"description":1007,"steps":1008},"How to property-test Django models","Use Hypothesis's Django TestCase for per-example isolation, derive strategies from models, override fields with domain constraints, and assert invariants.",[1009,1012,1015,1018,1021],{"name":1010,"text":1011},"Use the Hypothesis Django TestCase","Subclass hypothesis.extra.django.TestCase so each example runs in its own rolled-back transaction.",{"name":1013,"text":1014},"Derive strategies from models","Use from_model(Model) so generated instances respect field lengths, nullability and choices.",{"name":1016,"text":1017},"Override fields with domain rules","Pass keyword strategies for fields whose valid range is narrower than the column type.",{"name":1019,"text":1020},"Build related objects explicitly","Provide from_model strategies for required foreign keys.",{"name":1022,"text":1023},"Assert model invariants","Check properties such as clean() accepting saved instances, round-tripping through serializers, and computed fields staying consistent.","\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002Fproperty-testing-django-models-with-hypothesis",{"title":5,"description":991},"property-based-fuzz-testing-strategies\u002Fhypothesis-integration-with-pytest-and-frameworks\u002Fproperty-testing-django-models-with-hypothesis\u002Findex","J3czD4fDSfe2PKMvMlS76UW4tc8rc3UpHdaUc5fSTW8",1789718768961]