[{"data":1,"prerenderedAt":1090},["ShallowReactive",2],{"page-\u002Fadvanced-mocking-test-doubles-in-python\u002Ffaking-the-filesystem-and-environment\u002Ffaking-a-whole-filesystem-with-pyfakefs\u002F":3},{"id":4,"title":5,"body":6,"description":1054,"extension":1055,"meta":1056,"navigation":105,"path":1086,"seo":1087,"stem":1088,"__hash__":1089},"content\u002Fadvanced-mocking-test-doubles-in-python\u002Ffaking-the-filesystem-and-environment\u002Ffaking-a-whole-filesystem-with-pyfakefs\u002Findex.md","Faking a Whole Filesystem with pyfakefs",{"type":7,"value":8,"toc":1043},"minimark",[9,30,40,45,73,77,300,446,450,483,490,499,503,569,573,579,608,682,685,777,781,784,793,808,817,883,887,902,943,949,953,969,990,1001,1005,1034,1039],[10,11,12,13,17,18,21,22,25,26,29],"p",{},"Code that reads ",[14,15,16],"code",{},"\u002Fetc\u002Fmyapp\u002Fconfig.yaml",", writes to ",[14,19,20],{},"~\u002F.cache\u002Fmyapp",", or walks a directory tree expecting a particular layout is awkward to test against the real filesystem. Absolute paths cannot be pointed at a temporary directory without changing the code, creating ",[14,23,24],{},"\u002Fetc\u002F..."," in a test needs privileges nobody should grant, and leftover files from a failed run leak into the next one. ",[14,27,28],{},"pyfakefs"," replaces the filesystem modules with an in-memory implementation for the duration of a test, so the code reads and writes exactly the paths it always does, and none of them touch disk.",[10,31,32,33,36,37,39],{},"It is a powerful tool with a narrow sweet spot. For code that already accepts a directory as a parameter, ",[14,34,35],{},"tmp_path"," is simpler, faster to reason about, and uses the real filesystem — which is sometimes the point. ",[14,38,28],{}," earns its place where paths are fixed, layouts are elaborate, or the behaviour under another operating system's path rules must be tested.",[41,42,44],"h2",{"id":43},"prerequisites","Prerequisites",[46,47,48,59,65],"ul",{},[49,50,51,54,55,58],"li",{},[14,52,53],{},"pyfakefs >= 5.3",", which provides the ",[14,56,57],{},"fs"," pytest fixture automatically once installed.",[49,60,61,64],{},[14,62,63],{},"pytest >= 8.0",".",[49,66,67,68,64],{},"The simpler alternative in ",[69,70,72],"a",{"href":71},"\u002Fadvanced-mocking-test-doubles-in-python\u002Ffaking-the-filesystem-and-environment\u002Fusing-tmp-path-instead-of-tempfile-in-tests\u002F","using tmp_path instead of tempfile in tests",[41,74,76],{"id":75},"solution","Solution",[78,79,84],"pre",{"className":80,"code":81,"language":82,"meta":83,"style":83},"language-python shiki shiki-themes github-light github-dark","import os\nfrom pathlib import Path\n\n\ndef load_config() -> dict:\n    # Fixed absolute path: hard to test without faking the filesystem.\n    path = Path(\"\u002Fetc\u002Fmyapp\u002Fconfig.yaml\")\n    if not path.exists():\n        return {\"mode\": \"default\"}\n    return parse_yaml(path.read_text())\n\n\ndef rotate_logs(directory: Path, keep: int = 3) -> None:\n    logs = sorted(directory.glob(\"app.*.log\"))\n    for old in logs[:-keep]:\n        old.unlink()\n\n\ndef test_config_is_read_from_etc(fs):\n    fs.create_file(\"\u002Fetc\u002Fmyapp\u002Fconfig.yaml\", contents=\"mode: strict\\n\")\n    assert load_config() == {\"mode\": \"strict\"}\n\n\ndef test_missing_config_falls_back(fs):\n    assert load_config() == {\"mode\": \"default\"}      # empty fake filesystem\n\n\ndef test_rotation_keeps_the_newest_three(fs):\n    logdir = Path(\"\u002Fvar\u002Flog\u002Fmyapp\")\n    for day in range(1, 6):\n        fs.create_file(logdir \u002F f\"app.2026-09-0{day}.log\")\n\n    rotate_logs(logdir, keep=3)\n\n    assert sorted(p.name for p in logdir.iterdir()) == [\n        \"app.2026-09-03.log\", \"app.2026-09-04.log\", \"app.2026-09-05.log\",\n    ]\n","python","",[14,85,86,94,100,107,112,118,124,130,136,142,148,153,158,164,170,176,182,187,192,198,204,210,215,220,226,232,237,242,248,254,260,266,271,277,282,288,294],{"__ignoreMap":83},[87,88,91],"span",{"class":89,"line":90},"line",1,[87,92,93],{},"import os\n",[87,95,97],{"class":89,"line":96},2,[87,98,99],{},"from pathlib import Path\n",[87,101,103],{"class":89,"line":102},3,[87,104,106],{"emptyLinePlaceholder":105},true,"\n",[87,108,110],{"class":89,"line":109},4,[87,111,106],{"emptyLinePlaceholder":105},[87,113,115],{"class":89,"line":114},5,[87,116,117],{},"def load_config() -> dict:\n",[87,119,121],{"class":89,"line":120},6,[87,122,123],{},"    # Fixed absolute path: hard to test without faking the filesystem.\n",[87,125,127],{"class":89,"line":126},7,[87,128,129],{},"    path = Path(\"\u002Fetc\u002Fmyapp\u002Fconfig.yaml\")\n",[87,131,133],{"class":89,"line":132},8,[87,134,135],{},"    if not path.exists():\n",[87,137,139],{"class":89,"line":138},9,[87,140,141],{},"        return {\"mode\": \"default\"}\n",[87,143,145],{"class":89,"line":144},10,[87,146,147],{},"    return parse_yaml(path.read_text())\n",[87,149,151],{"class":89,"line":150},11,[87,152,106],{"emptyLinePlaceholder":105},[87,154,156],{"class":89,"line":155},12,[87,157,106],{"emptyLinePlaceholder":105},[87,159,161],{"class":89,"line":160},13,[87,162,163],{},"def rotate_logs(directory: Path, keep: int = 3) -> None:\n",[87,165,167],{"class":89,"line":166},14,[87,168,169],{},"    logs = sorted(directory.glob(\"app.*.log\"))\n",[87,171,173],{"class":89,"line":172},15,[87,174,175],{},"    for old in logs[:-keep]:\n",[87,177,179],{"class":89,"line":178},16,[87,180,181],{},"        old.unlink()\n",[87,183,185],{"class":89,"line":184},17,[87,186,106],{"emptyLinePlaceholder":105},[87,188,190],{"class":89,"line":189},18,[87,191,106],{"emptyLinePlaceholder":105},[87,193,195],{"class":89,"line":194},19,[87,196,197],{},"def test_config_is_read_from_etc(fs):\n",[87,199,201],{"class":89,"line":200},20,[87,202,203],{},"    fs.create_file(\"\u002Fetc\u002Fmyapp\u002Fconfig.yaml\", contents=\"mode: strict\\n\")\n",[87,205,207],{"class":89,"line":206},21,[87,208,209],{},"    assert load_config() == {\"mode\": \"strict\"}\n",[87,211,213],{"class":89,"line":212},22,[87,214,106],{"emptyLinePlaceholder":105},[87,216,218],{"class":89,"line":217},23,[87,219,106],{"emptyLinePlaceholder":105},[87,221,223],{"class":89,"line":222},24,[87,224,225],{},"def test_missing_config_falls_back(fs):\n",[87,227,229],{"class":89,"line":228},25,[87,230,231],{},"    assert load_config() == {\"mode\": \"default\"}      # empty fake filesystem\n",[87,233,235],{"class":89,"line":234},26,[87,236,106],{"emptyLinePlaceholder":105},[87,238,240],{"class":89,"line":239},27,[87,241,106],{"emptyLinePlaceholder":105},[87,243,245],{"class":89,"line":244},28,[87,246,247],{},"def test_rotation_keeps_the_newest_three(fs):\n",[87,249,251],{"class":89,"line":250},29,[87,252,253],{},"    logdir = Path(\"\u002Fvar\u002Flog\u002Fmyapp\")\n",[87,255,257],{"class":89,"line":256},30,[87,258,259],{},"    for day in range(1, 6):\n",[87,261,263],{"class":89,"line":262},31,[87,264,265],{},"        fs.create_file(logdir \u002F f\"app.2026-09-0{day}.log\")\n",[87,267,269],{"class":89,"line":268},32,[87,270,106],{"emptyLinePlaceholder":105},[87,272,274],{"class":89,"line":273},33,[87,275,276],{},"    rotate_logs(logdir, keep=3)\n",[87,278,280],{"class":89,"line":279},34,[87,281,106],{"emptyLinePlaceholder":105},[87,283,285],{"class":89,"line":284},35,[87,286,287],{},"    assert sorted(p.name for p in logdir.iterdir()) == [\n",[87,289,291],{"class":89,"line":290},36,[87,292,293],{},"        \"app.2026-09-03.log\", \"app.2026-09-04.log\", \"app.2026-09-05.log\",\n",[87,295,297],{"class":89,"line":296},37,[87,298,299],{},"    ]\n",[301,302,305,442],"figure",{"className":303},[304],"diagram",[306,307,314,315,314,319,314,323,314,341,314,349,314,359,314,368,314,374,314,379,314,383,314,387,314,394,314,400,314,404,314,407,314,410,314,414,314,422,314,427,314,431,314,437],"svg",{"viewBox":308,"role":309,"ariaLabelledBy":310,"xmlns":313},"0 0 820 262","img",[311,312],"pf-t","pf-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[316,317,318],"title",{"id":311},"What pyfakefs replaces during a test",[320,321,322],"desc",{"id":312},"Code under test calls open, os, os.path, pathlib and shutil as usual. With the fs fixture active, those modules are redirected to an in-memory filesystem holding only the files the test created. The real disk is never touched, and the fake is discarded at teardown.",[324,325,326,327,314],"defs",{},"\n    ",[328,329,336],"marker",{"id":330,"viewBox":331,"refX":332,"refY":333,"markerWidth":334,"markerHeight":334,"orient":335},"pf-a","0 0 10 10","9","5","7","auto-start-reverse",[337,338],"path",{"d":339,"fill":340},"M0 0 L10 5 L0 10 z","#81b29a",[342,343],"rect",{"x":344,"y":344,"width":345,"height":346,"rx":347,"fill":348},"0","820","262","14","#fffdf8",[350,351,358],"text",{"x":352,"y":353,"textAnchor":354,"fontSize":355,"fontWeight":356,"fill":357},"410","28","middle","16","700","#3d405b","The same calls, a different filesystem",[342,360],{"x":361,"y":362,"width":363,"height":364,"rx":365,"fill":366,"stroke":357,"strokeWidth":367},"26","70","220","120","11","#f4f1de","1.6",[350,369,373],{"x":370,"y":371,"textAnchor":354,"fontSize":372,"fontWeight":356,"fill":357},"136","96","12","code under test",[350,375,378],{"x":376,"y":377,"fontSize":365,"fill":357},"44","122","open(\"\u002Fetc\u002F…\")",[350,380,382],{"x":376,"y":381,"fontSize":365,"fill":357},"142","Path.glob, os.walk",[350,384,386],{"x":376,"y":385,"fontSize":365,"fill":357},"162","shutil.copy",[89,388],{"x1":389,"y1":390,"x2":391,"y2":390,"stroke":340,"strokeWidth":392,"markerEnd":393},"250","130","316","1.8","url(#pf-a)",[342,395],{"x":396,"y":362,"width":397,"height":364,"rx":365,"fill":398,"stroke":340,"strokeWidth":399},"322","240","#e6f0ea","2",[350,401,403],{"x":402,"y":371,"textAnchor":354,"fontSize":372,"fontWeight":356,"fill":357},"442","in-memory filesystem",[350,405,16],{"x":406,"y":377,"fontSize":365,"fill":357},"340",[350,408,409],{"x":406,"y":381,"fontSize":365,"fill":357},"\u002Fvar\u002Flog\u002Fmyapp\u002Fapp.*.log",[350,411,413],{"x":406,"y":385,"fontSize":365,"fill":412},"#2a5f49","only what the test created",[342,415],{"x":416,"y":362,"width":417,"height":364,"rx":365,"fill":348,"stroke":418,"strokeWidth":419,"strokeDashArray":420},"590","204","rgba(61,64,91,0.35)","1.5",[333,421],"4",[350,423,426],{"x":424,"y":425,"textAnchor":354,"fontSize":372,"fontWeight":356,"fill":357},"692","116","real disk",[350,428,430],{"x":424,"y":429,"textAnchor":354,"fontSize":365,"fill":357},"140","untouched",[342,432],{"x":361,"y":433,"width":434,"height":435,"rx":332,"fill":348,"stroke":418,"strokeWidth":436},"210","768","34","1.4",[350,438,441],{"x":352,"y":439,"textAnchor":354,"fontSize":440,"fill":357},"232","11.5","Discarded at teardown: no leftover files, no privileges needed for \u002Fetc.",[443,444,445],"figcaption",{},"The code does not change and does not know. That is both the tool's strength and the reason to keep its use narrow.",[41,447,449],{"id":448},"why-this-works","Why this works",[10,451,452,454,455,458,459,458,462,458,465,458,468,471,472,475,476,478,479,482],{},[14,453,28],{}," patches the modules through which Python code touches files — ",[14,456,457],{},"os",", ",[14,460,461],{},"os.path",[14,463,464],{},"pathlib",[14,466,467],{},"shutil",[14,469,470],{},"io",", and the built-in ",[14,473,474],{},"open"," — replacing them with implementations backed by an in-memory tree. The ",[14,477,57],{}," fixture activates the patch before the test and removes it afterwards, and it also patches those names in modules that imported them directly, so ",[14,480,481],{},"from os import path"," in the code under test still sees the fake.",[10,484,485,486,489],{},"Because the tree starts empty, every file the code sees was created by the test. That makes the test's preconditions explicit and complete: a reader knows exactly which files exist, and nothing on the developer's machine or the CI runner can leak in. It also makes absolute paths safe, since ",[14,487,488],{},"\u002Fetc"," in the fake is just a directory the test created.",[10,491,492,493,495,496,498],{},"That isolation cuts both ways, and it is worth being explicit about. A test running under ",[14,494,28],{}," proves the code behaves correctly against the fake's model of a filesystem, which is very good but not identical to every real one: case sensitivity, symlink resolution, extended attributes and filesystem-specific limits may differ. For logic that depends on those details, a real ",[14,497,35],{}," on the target platform is the stronger test, and the two approaches are complementary rather than competing. Most suites need a little of each.",[41,500,502],{"id":501},"edge-cases-and-failure-modes","Edge cases and failure modes",[46,504,505,514,520,537,551,559],{},[49,506,507,511,512,64],{},[508,509,510],"strong",{},"C extensions opening files."," Libraries that open files from C — some image and database libraries — bypass the patch and hit the real disk. Test those with ",[14,513,35],{},[49,515,516,519],{},[508,517,518],{},"Paths in other processes."," A subprocess sees the real filesystem. Anything that shells out needs real files.",[49,521,522,525,526,529,530,532,533,536],{},[508,523,524],{},"Modules cached before the patch."," A module that stored a reference to ",[14,527,528],{},"os.stat"," in a private variable at import may escape. ",[14,531,28],{}," handles common cases; ",[14,534,535],{},"Patcher(modules_to_reload=...)"," covers the rest.",[49,538,539,542,543,546,547,550],{},[508,540,541],{},"Temp directories."," ",[14,544,545],{},"tempfile"," works inside the fake, but code that relies on the real ",[14,548,549],{},"\u002Ftmp"," being writable by another process will not.",[49,552,553,556,557,64],{},[508,554,555],{},"Overusing it."," Faking the filesystem for code that could take a directory argument adds magic without benefit. Prefer the parameter and ",[14,558,35],{},[49,560,561,564,565,568],{},[508,562,563],{},"Platform-specific behaviour."," The fake emulates the host OS by default. Set ",[14,566,567],{},"fs.os = OSType.WINDOWS"," to test drive letters, backslash separators and case-insensitive lookups from a Linux CI runner, without needing a Windows machine.",[41,570,572],{"id":571},"testing-error-paths-the-real-disk-makes-hard","Testing error paths the real disk makes hard",[10,574,575,576,578],{},"The most valuable thing a fake filesystem offers is not avoiding real files — ",[14,577,35],{}," does that — but producing conditions that are awkward or impossible to create on demand. Permission errors, full disks and read-only mounts are all situations production code must handle, and all are painful to arrange for real in a test.",[10,580,581,583,584,587,588,591,592,595,596,599,600,603,604,607],{},[14,582,28],{}," makes each a line of setup. ",[14,585,586],{},"fs.create_file(path, st_mode=0o000)"," produces a file the code cannot read, and — as long as the test is not running as root inside the fake — opening it raises ",[14,589,590],{},"PermissionError"," exactly as it would on a real system. ",[14,593,594],{},"fs.set_disk_usage(total_size=1024)"," caps the fake disk, so a write that exceeds it raises ",[14,597,598],{},"OSError"," with ",[14,601,602],{},"ENOSPC",", the out-of-space error. ",[14,605,606],{},"fs.add_real_directory(path, read_only=True)"," maps a repository fixture in such a way that any attempt to modify it fails.",[78,609,611],{"className":80,"code":610,"language":82,"meta":83,"style":83},"import errno\nfrom pathlib import Path\n\nimport pytest\n\n\ndef test_export_reports_a_full_disk(fs):\n    fs.set_disk_usage(total_size=100)            # a tiny disk\n    fs.create_dir(\"\u002Fexports\")\n\n    with pytest.raises(ExportFailed) as excinfo:\n        export_report(Path(\"\u002Fexports\u002Freport.csv\"), rows=make_rows(1_000))\n\n    assert excinfo.value.__cause__.errno == errno.ENOSPC\n    assert not Path(\"\u002Fexports\u002Freport.csv\").exists()   # no half-written file left\n",[14,612,613,618,622,626,631,635,639,644,649,654,658,663,668,672,677],{"__ignoreMap":83},[87,614,615],{"class":89,"line":90},[87,616,617],{},"import errno\n",[87,619,620],{"class":89,"line":96},[87,621,99],{},[87,623,624],{"class":89,"line":102},[87,625,106],{"emptyLinePlaceholder":105},[87,627,628],{"class":89,"line":109},[87,629,630],{},"import pytest\n",[87,632,633],{"class":89,"line":114},[87,634,106],{"emptyLinePlaceholder":105},[87,636,637],{"class":89,"line":120},[87,638,106],{"emptyLinePlaceholder":105},[87,640,641],{"class":89,"line":126},[87,642,643],{},"def test_export_reports_a_full_disk(fs):\n",[87,645,646],{"class":89,"line":132},[87,647,648],{},"    fs.set_disk_usage(total_size=100)            # a tiny disk\n",[87,650,651],{"class":89,"line":138},[87,652,653],{},"    fs.create_dir(\"\u002Fexports\")\n",[87,655,656],{"class":89,"line":144},[87,657,106],{"emptyLinePlaceholder":105},[87,659,660],{"class":89,"line":150},[87,661,662],{},"    with pytest.raises(ExportFailed) as excinfo:\n",[87,664,665],{"class":89,"line":155},[87,666,667],{},"        export_report(Path(\"\u002Fexports\u002Freport.csv\"), rows=make_rows(1_000))\n",[87,669,670],{"class":89,"line":160},[87,671,106],{"emptyLinePlaceholder":105},[87,673,674],{"class":89,"line":166},[87,675,676],{},"    assert excinfo.value.__cause__.errno == errno.ENOSPC\n",[87,678,679],{"class":89,"line":172},[87,680,681],{},"    assert not Path(\"\u002Fexports\u002Freport.csv\").exists()   # no half-written file left\n",[10,683,684],{},"The final assertion is the one that matters. Code that writes directly to the destination leaves a truncated file behind when the disk fills; code that writes to a temporary file and renames it on success leaves nothing. The test distinguishes them in milliseconds, and without a fake it would require actually filling a disk.",[301,686,688,774],{"className":687},[304],[306,689,314,694,314,697,314,700,314,704,314,709,314,715,314,718,314,722,314,727,314,731,314,735,314,739,314,741,314,744,314,748,314,751,314,755,314,758,314,760,314,764,314,768,314,771],{"viewBox":690,"role":309,"ariaLabelledBy":691,"xmlns":313},"0 0 800 236",[692,693],"err-t","err-d",[316,695,696],{"id":692},"Filesystem failures a fake can produce on demand",[320,698,699],{"id":693},"Three conditions. A file created with no permissions makes reads raise PermissionError. A capped disk size makes large writes raise an out-of-space error. A read-only mapped directory makes modifications fail. Each is one line of setup with pyfakefs and hard to arrange on a real system.",[342,701],{"x":344,"y":344,"width":702,"height":703,"rx":347,"fill":348},"800","236",[350,705,708],{"x":706,"y":353,"textAnchor":354,"fontSize":707,"fontWeight":356,"fill":357},"400","15.5","One line of setup per failure mode",[342,710],{"x":711,"y":712,"width":397,"height":713,"rx":372,"fill":348,"stroke":714,"strokeWidth":399},"24","50","164","#e07a5f",[342,716],{"x":711,"y":712,"width":397,"height":717,"rx":372,"fill":357},"30",[350,719,721],{"x":720,"y":362,"textAnchor":354,"fontSize":372,"fontWeight":356,"fill":348},"144","permission denied",[350,723,726],{"x":724,"y":725,"fontSize":365,"fill":357},"40","104","create_file(…,",[350,728,730],{"x":724,"y":729,"fontSize":365,"fill":357},"124","  st_mode=0o000)",[350,732,590],{"x":724,"y":733,"fontSize":365,"fill":734},"170","#8f3d22",[342,736],{"x":737,"y":712,"width":397,"height":713,"rx":372,"fill":348,"stroke":738,"strokeWidth":399},"280","#f2cc8f",[342,740],{"x":737,"y":712,"width":397,"height":717,"rx":372,"fill":357},[350,742,743],{"x":706,"y":362,"textAnchor":354,"fontSize":372,"fontWeight":356,"fill":348},"disk full",[350,745,747],{"x":746,"y":725,"fontSize":365,"fill":357},"296","set_disk_usage(",[350,749,750],{"x":746,"y":729,"fontSize":365,"fill":357},"  total_size=100)",[350,752,754],{"x":746,"y":733,"fontSize":365,"fill":753},"#8a5a00","OSError ENOSPC",[342,756],{"x":757,"y":712,"width":397,"height":713,"rx":372,"fill":348,"stroke":340,"strokeWidth":399},"536",[342,759],{"x":757,"y":712,"width":397,"height":717,"rx":372,"fill":357},[350,761,763],{"x":762,"y":362,"textAnchor":354,"fontSize":372,"fontWeight":356,"fill":348},"656","read-only",[350,765,767],{"x":766,"y":725,"fontSize":365,"fill":357},"552","add_real_directory(…,",[350,769,770],{"x":766,"y":729,"fontSize":365,"fill":357},"  read_only=True)",[350,772,773],{"x":766,"y":733,"fontSize":365,"fill":412},"writes fail",[443,775,776],{},"These are the conditions production hits during incidents, and the ones a real-filesystem test suite almost never exercises.",[41,778,780],{"id":779},"pyfakefs-or-tmp_path","pyfakefs or tmp_path",[10,782,783],{},"The two tools answer different questions, and choosing between them is mostly about the code's design rather than the test's preference.",[10,785,786,788,789,792],{},[14,787,35],{}," gives each test a real, empty, unique directory that pytest cleans up. It is the right default for any code that accepts a path argument: the test passes ",[14,790,791],{},"tmp_path \u002F \"config.yaml\"",", and the code exercises the real filesystem with its real semantics — permissions, symlinks, case sensitivity, atomic renames. Nothing is patched, so nothing can escape the patch.",[10,794,795,797,798,800,801,804,805,807],{},[14,796,28],{}," is right when the path is not a parameter. Configuration read from ",[14,799,488],{},", caches written under ",[14,802,803],{},"~",", a tool that walks from the filesystem root — these can only be tested with a real filesystem by changing the machine, and a fake is the lesser evil. It is also the only practical way to test Windows path behaviour on a Linux runner, since ",[14,806,567],{}," switches separator and drive-letter rules.",[10,809,810,811,813,814,816],{},"A useful rule of thumb: if introducing a directory parameter is a small change, make it and use ",[14,812,35],{},". The code gains a seam that also helps production — configurable locations are rarely a bad thing — and the tests lose a layer of patching. Reserve ",[14,815,28],{}," for fixed paths that genuinely cannot move, and for cross-platform path logic.",[301,818,820,880],{"className":819},[304],[306,821,314,825,314,828,314,831,314,833,314,836,314,839,314,845,314,848,314,852,314,855,314,859,314,863,314,867,314,871,314,874,314,877],{"viewBox":690,"role":309,"ariaLabelledBy":822,"xmlns":313},[823,824],"choice-t","choice-d",[316,826,827],{"id":823},"Choosing between tmp_path and pyfakefs",[320,829,830],{"id":824},"If the code accepts a path argument, or a small change can make it do so, use tmp_path and the real filesystem. If the path is fixed, such as a system configuration location, or the test must emulate another operating system's path rules, use pyfakefs.",[342,832],{"x":344,"y":344,"width":702,"height":703,"rx":347,"fill":348},[350,834,835],{"x":706,"y":353,"textAnchor":354,"fontSize":707,"fontWeight":356,"fill":357},"Can the path be a parameter?",[342,837],{"x":361,"y":712,"width":838,"height":713,"rx":372,"fill":398,"stroke":340,"strokeWidth":399},"360",[350,840,844],{"x":841,"y":842,"textAnchor":354,"fontSize":843,"fontWeight":356,"fill":357},"206","76","12.5","yes → tmp_path",[350,846,847],{"x":376,"y":725,"fontSize":365,"fill":357},"real filesystem semantics",[350,849,851],{"x":376,"y":850,"fontSize":365,"fill":357},"126","nothing patched, nothing escapes",[350,853,854],{"x":376,"y":713,"fontSize":365,"fontWeight":356,"fill":412},"the default choice",[350,856,858],{"x":376,"y":857,"fontSize":365,"fill":357},"186","configurable paths help production too",[342,860],{"x":861,"y":712,"width":838,"height":713,"rx":372,"fill":862,"stroke":738,"strokeWidth":399},"414","#f7f0da",[350,864,866],{"x":865,"y":842,"textAnchor":354,"fontSize":843,"fontWeight":356,"fill":357},"594","no → pyfakefs",[350,868,870],{"x":869,"y":725,"fontSize":365,"fill":357},"432","\u002Fetc, ~, fixed tool layouts",[350,872,873],{"x":869,"y":850,"fontSize":365,"fill":357},"Windows rules on a Linux runner",[350,875,876],{"x":869,"y":713,"fontSize":365,"fontWeight":356,"fill":753},"the targeted choice",[350,878,879],{"x":869,"y":857,"fontSize":365,"fill":357},"watch for C-level file access",[443,881,882],{},"Most file-handling code belongs on the left once it takes its paths as arguments.",[41,884,886],{"id":885},"preloading-fixtures-without-copying-them","Preloading fixtures without copying them",[10,888,889,890,893,894,897,898,901],{},"Many tests need a realistic file to read — a sample configuration, a recorded export, a malformed input found in production. Copying those into the fake by hand with ",[14,891,892],{},"create_file(contents=...)"," works for short strings and becomes unmaintainable for anything larger. ",[14,895,896],{},"fs.add_real_file"," and ",[14,899,900],{},"fs.add_real_directory"," solve it by mapping files from the repository into the fake filesystem, optionally at a different path.",[78,903,905],{"className":80,"code":904,"language":82,"meta":83,"style":83},"from pathlib import Path\n\nFIXTURES = Path(__file__).parent \u002F \"fixtures\"\n\n\ndef test_parses_the_production_sample(fs):\n    fs.add_real_file(FIXTURES \u002F \"config.prod.yaml\", target_path=\"\u002Fetc\u002Fmyapp\u002Fconfig.yaml\")\n    assert load_config()[\"mode\"] == \"strict\"\n",[14,906,907,911,915,920,924,928,933,938],{"__ignoreMap":83},[87,908,909],{"class":89,"line":90},[87,910,99],{},[87,912,913],{"class":89,"line":96},[87,914,106],{"emptyLinePlaceholder":105},[87,916,917],{"class":89,"line":102},[87,918,919],{},"FIXTURES = Path(__file__).parent \u002F \"fixtures\"\n",[87,921,922],{"class":89,"line":109},[87,923,106],{"emptyLinePlaceholder":105},[87,925,926],{"class":89,"line":114},[87,927,106],{"emptyLinePlaceholder":105},[87,929,930],{"class":89,"line":120},[87,931,932],{},"def test_parses_the_production_sample(fs):\n",[87,934,935],{"class":89,"line":126},[87,936,937],{},"    fs.add_real_file(FIXTURES \u002F \"config.prod.yaml\", target_path=\"\u002Fetc\u002Fmyapp\u002Fconfig.yaml\")\n",[87,939,940],{"class":89,"line":132},[87,941,942],{},"    assert load_config()[\"mode\"] == \"strict\"\n",[10,944,945,946,948],{},"The mapping is lazy — the real file is read only when the code opens it — and read-only by default, so a test cannot accidentally modify a fixture in the repository. That combination keeps fixtures in version control as ordinary files, reviewable in diffs, while the code under test sees them at the absolute paths it expects. It is the piece that makes ",[14,947,28],{}," practical for suites with more than a handful of file-driven tests.",[41,950,952],{"id":951},"frequently-asked-questions","Frequently Asked Questions",[10,954,955,958,959,961,962,965,966,968],{},[508,956,957],{},"When should I use pyfakefs instead of tmp_path?","\nWhen the code under test uses fixed absolute paths such as ",[14,960,16],{}," or ",[14,963,964],{},"~\u002F.cache",", when it needs to see a directory layout that would be awkward to create for real, or when it must be tested against another operating system's path rules. For code that accepts a directory argument, ",[14,967,35],{}," is simpler and uses the real filesystem.",[10,970,971,974,975,458,977,458,979,458,981,458,983,986,987,989],{},[508,972,973],{},"Does pyfakefs work with pathlib and shutil?","\nYes. It patches ",[14,976,457],{},[14,978,461],{},[14,980,464],{},[14,982,467],{},[14,984,985],{},"io.open"," and the built-in ",[14,988,474],{},", so ordinary file code sees the fake filesystem. Modules that open files through C extensions bypass it and need either a real temporary directory or explicit handling.",[10,991,992,995,996,897,998,1000],{},[508,993,994],{},"Can I load real files into the fake filesystem?","\nYes. ",[14,997,896],{},[14,999,900],{}," map real paths into the fake one, read-only by default, which is the way to give tests access to fixtures stored in the repository without copying them.",[41,1002,1004],{"id":1003},"related","Related",[46,1006,1007,1014,1020,1027],{},[49,1008,1009,1013],{},[69,1010,1012],{"href":1011},"\u002Fadvanced-mocking-test-doubles-in-python\u002Ffaking-the-filesystem-and-environment\u002F","Faking the Filesystem and Environment"," — the wider set of isolation tools.",[49,1015,1016,1019],{},[69,1017,1018],{"href":71},"Using tmp_path Instead of tempfile in Tests"," — the real-filesystem alternative.",[49,1021,1022,1026],{},[69,1023,1025],{"href":1024},"\u002Fadvanced-mocking-test-doubles-in-python\u002Ffaking-the-filesystem-and-environment\u002Fpatching-environment-variables-with-monkeypatch-setenv\u002F","Patching Environment Variables with monkeypatch.setenv"," — isolating HOME and other path-defining variables.",[49,1028,1029,1033],{},[69,1030,1032],{"href":1031},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002F","Dependency Injection for Testability"," — turning fixed paths into parameters.",[10,1035,1036,1037],{},"← Back to ",[69,1038,1012],{"href":1011},[1040,1041,1042],"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":83,"searchDepth":96,"depth":96,"links":1044},[1045,1046,1047,1048,1049,1050,1051,1052,1053],{"id":43,"depth":96,"text":44},{"id":75,"depth":96,"text":76},{"id":448,"depth":96,"text":449},{"id":501,"depth":96,"text":502},{"id":571,"depth":96,"text":572},{"id":779,"depth":96,"text":780},{"id":885,"depth":96,"text":886},{"id":951,"depth":96,"text":952},{"id":1003,"depth":96,"text":1004},"Replace the real filesystem with an in-memory one using pyfakefs: the fs fixture, preloading files, permissions and OS-specific paths, and when tmp_path is the better choice.","md",{"slug":1057,"type":1058,"breadcrumb":28,"datePublished":1059,"dateModified":1059,"faq":1060,"howto":1067},"faking-a-whole-filesystem-with-pyfakefs","article","2026-09-18",[1061,1063,1065],{"q":957,"a":1062},"When the code under test uses fixed absolute paths such as \u002Fetc\u002Fmyapp\u002Fconfig.yaml or ~\u002F.cache, when it needs to see a directory layout that would be awkward to create for real, or when it must be tested against another operating system's path rules. For code that accepts a directory argument, tmp_path is simpler and uses the real filesystem.",{"q":973,"a":1064},"Yes. It patches os, os.path, pathlib, shutil, io.open and the built-in open, so ordinary file code sees the fake filesystem. Modules that open files through C extensions bypass it and need either a real temporary directory or explicit handling.",{"q":994,"a":1066},"Yes. fs.add_real_file and fs.add_real_directory map real paths into the fake one, read-only by default, which is the way to give tests access to fixtures stored in the repository without copying them.",{"name":1068,"description":1069,"steps":1070},"How to test file-handling code with pyfakefs","Request the fs fixture, create the layout the code expects, run it, and assert on the resulting files.",[1071,1074,1077,1080,1083],{"name":1072,"text":1073},"Request the fs fixture","Add fs to the test's parameters; every standard file API now operates on an empty in-memory filesystem.",{"name":1075,"text":1076},"Create the expected layout","Use fs.create_file and fs.create_dir to build exactly the directories and files the code reads.",{"name":1078,"text":1079},"Map real fixture files if needed","Use fs.add_real_file for repository fixtures the code must read.",{"name":1081,"text":1082},"Run the code under test","Call it normally; it reads and writes the fake filesystem without knowing.",{"name":1084,"text":1085},"Assert on files and contents","Check existence, contents and permissions with ordinary os and pathlib calls.","\u002Fadvanced-mocking-test-doubles-in-python\u002Ffaking-the-filesystem-and-environment\u002Ffaking-a-whole-filesystem-with-pyfakefs",{"title":5,"description":1054},"advanced-mocking-test-doubles-in-python\u002Ffaking-the-filesystem-and-environment\u002Ffaking-a-whole-filesystem-with-pyfakefs\u002Findex","m4mxZvJWjU13i8sQw-Z3e1GPzMzcab-cMTRcMcFNKGA",1789718768914]