[{"data":1,"prerenderedAt":3441},["ShallowReactive",2],{"page-\u002Fjs\u002F25-testing":3},{"id":4,"title":5,"body":6,"description":3434,"extension":3435,"meta":3436,"navigation":105,"path":3437,"seo":3438,"stem":3439,"__hash__":3440},"content\u002Fjs\u002F25-testing.md","JavaScript 25 — Testing Architecture: Vitest, Mocking & TDD Patterns",{"type":7,"value":8,"toc":3423},"minimark",[9,14,19,586,600,1118,1122,1594,1598,2110,2114,2483,2487,2922,2926,3173,3177,3181,3283,3419],[10,11,13],"h1",{"id":12},"_25-testing-architecture-vitest-mocking-tdd-patterns","25 — Testing Architecture: Vitest, Mocking & TDD Patterns",[15,16,18],"h2",{"id":17},"test-types-and-their-boundaries","Test Types and Their Boundaries",[20,21,23],"code-wrapper",{"language":22},"javascript",[24,25,29],"pre",{"className":26,"code":27,"language":22,"meta":28,"style":28},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F ── Test pyramid (most tests at the bottom, fewest at the top) ──\n\u002F\u002F\n\u002F\u002F        E2E (few)     — full browser\u002Fnetwork (Playwright, Cypress)\n\u002F\u002F       \u002F          \\\n\u002F\u002F   Integration (some) — multiple units together (API + DB, component + store)\n\u002F\u002F  \u002F                \\\n\u002F\u002F Unit (many)           — isolated function\u002Fclass (mocked dependencies)\n\u002F\u002F\n\u002F\u002F ── Unit test: test one function in isolation (dependencies mocked) ──\n\u002F\u002F ── Integration test: test multiple units together (real dependencies) ──\n\u002F\u002F ── E2E test: test the full user flow (browser, server, database) ──\n\n\u002F\u002F ── Vitest test structure ──\nimport { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { calculateTotal, applyDiscount } from \".\u002Fmath.js\";\n\ndescribe(\"calculateTotal\", () => {  \u002F\u002F group related tests\n    it(\"sums an array of prices\", () => {  \u002F\u002F individual test\n        expect(calculateTotal([10, 20, 30])).toBe(60);\n    });\n\n    it(\"returns 0 for empty array\", () => {\n        expect(calculateTotal([])).toBe(0);\n    });\n\n    it(\"handles negative values\", () => {\n        expect(calculateTotal([-10, 20])).toBe(10);\n    });\n});\n\n\u002F\u002F ── Vitest assertions ──\nexpect(value).toBe(expected);        \u002F\u002F strict equality (===)\nexpect(value).toEqual(expected);    \u002F\u002F deep equality (object\u002Farray content)\nexpect(value).toBeTruthy();         \u002F\u002F truthy check\nexpect(value).toBeFalsy();          \u002F\u002F falsy check\nexpect(value).toBeNull();           \u002F\u002F === null\nexpect(value).toBeUndefined();      \u002F\u002F === undefined\nexpect(value).toBeNaN();            \u002F\u002F Number.isNaN\nexpect(array).toContain(item);     \u002F\u002F array.includes(item) or string.includes\nexpect(array).toHaveLength(3);     \u002F\u002F array.length === 3\nexpect(fn).toThrow(\"error msg\");   \u002F\u002F function throws\nexpect(value).toMatch(\u002Fregex\u002F);    \u002F\u002F regex match\nexpect(object).toMatchObject({ key: value });  \u002F\u002F partial object match\n","",[30,31,32,41,47,53,59,65,71,77,82,88,94,100,107,113,134,149,154,179,199,242,248,253,270,291,296,301,317,347,352,358,363,369,386,402,418,434,450,466,482,499,520,542,569],"code",{"__ignoreMap":28},[33,34,37],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sdCPZ","\u002F\u002F ── Test pyramid (most tests at the bottom, fewest at the top) ──\n",[33,42,44],{"class":35,"line":43},2,[33,45,46],{"class":39},"\u002F\u002F\n",[33,48,50],{"class":35,"line":49},3,[33,51,52],{"class":39},"\u002F\u002F        E2E (few)     — full browser\u002Fnetwork (Playwright, Cypress)\n",[33,54,56],{"class":35,"line":55},4,[33,57,58],{"class":39},"\u002F\u002F       \u002F          \\\n",[33,60,62],{"class":35,"line":61},5,[33,63,64],{"class":39},"\u002F\u002F   Integration (some) — multiple units together (API + DB, component + store)\n",[33,66,68],{"class":35,"line":67},6,[33,69,70],{"class":39},"\u002F\u002F  \u002F                \\\n",[33,72,74],{"class":35,"line":73},7,[33,75,76],{"class":39},"\u002F\u002F Unit (many)           — isolated function\u002Fclass (mocked dependencies)\n",[33,78,80],{"class":35,"line":79},8,[33,81,46],{"class":39},[33,83,85],{"class":35,"line":84},9,[33,86,87],{"class":39},"\u002F\u002F ── Unit test: test one function in isolation (dependencies mocked) ──\n",[33,89,91],{"class":35,"line":90},10,[33,92,93],{"class":39},"\u002F\u002F ── Integration test: test multiple units together (real dependencies) ──\n",[33,95,97],{"class":35,"line":96},11,[33,98,99],{"class":39},"\u002F\u002F ── E2E test: test the full user flow (browser, server, database) ──\n",[33,101,103],{"class":35,"line":102},12,[33,104,106],{"emptyLinePlaceholder":105},true,"\n",[33,108,110],{"class":35,"line":109},13,[33,111,112],{"class":39},"\u002F\u002F ── Vitest test structure ──\n",[33,114,116,120,124,127,131],{"class":35,"line":115},14,[33,117,119],{"class":118},"svdQ7","import",[33,121,123],{"class":122},"ssxIu"," { describe, it, expect, vi, beforeEach, afterEach } ",[33,125,126],{"class":118},"from",[33,128,130],{"class":129},"sJ6F3"," \"vitest\"",[33,132,133],{"class":122},";\n",[33,135,137,139,142,144,147],{"class":35,"line":136},15,[33,138,119],{"class":118},[33,140,141],{"class":122}," { calculateTotal, applyDiscount } ",[33,143,126],{"class":118},[33,145,146],{"class":129}," \".\u002Fmath.js\"",[33,148,133],{"class":122},[33,150,152],{"class":35,"line":151},16,[33,153,106],{"emptyLinePlaceholder":105},[33,155,157,161,164,167,170,173,176],{"class":35,"line":156},17,[33,158,160],{"class":159},"sIsaT","describe",[33,162,163],{"class":122},"(",[33,165,166],{"class":129},"\"calculateTotal\"",[33,168,169],{"class":122},", () ",[33,171,172],{"class":118},"=>",[33,174,175],{"class":122}," {  ",[33,177,178],{"class":39},"\u002F\u002F group related tests\n",[33,180,182,185,187,190,192,194,196],{"class":35,"line":181},18,[33,183,184],{"class":159},"    it",[33,186,163],{"class":122},[33,188,189],{"class":129},"\"sums an array of prices\"",[33,191,169],{"class":122},[33,193,172],{"class":118},[33,195,175],{"class":122},[33,197,198],{"class":39},"\u002F\u002F individual test\n",[33,200,202,205,207,210,213,217,220,223,225,228,231,234,236,239],{"class":35,"line":201},19,[33,203,204],{"class":159},"        expect",[33,206,163],{"class":122},[33,208,209],{"class":159},"calculateTotal",[33,211,212],{"class":122},"([",[33,214,216],{"class":215},"snvgF","10",[33,218,219],{"class":122},", ",[33,221,222],{"class":215},"20",[33,224,219],{"class":122},[33,226,227],{"class":215},"30",[33,229,230],{"class":122},"])).",[33,232,233],{"class":159},"toBe",[33,235,163],{"class":122},[33,237,238],{"class":215},"60",[33,240,241],{"class":122},");\n",[33,243,245],{"class":35,"line":244},20,[33,246,247],{"class":122},"    });\n",[33,249,251],{"class":35,"line":250},21,[33,252,106],{"emptyLinePlaceholder":105},[33,254,256,258,260,263,265,267],{"class":35,"line":255},22,[33,257,184],{"class":159},[33,259,163],{"class":122},[33,261,262],{"class":129},"\"returns 0 for empty array\"",[33,264,169],{"class":122},[33,266,172],{"class":118},[33,268,269],{"class":122}," {\n",[33,271,273,275,277,279,282,284,286,289],{"class":35,"line":272},23,[33,274,204],{"class":159},[33,276,163],{"class":122},[33,278,209],{"class":159},[33,280,281],{"class":122},"([])).",[33,283,233],{"class":159},[33,285,163],{"class":122},[33,287,288],{"class":215},"0",[33,290,241],{"class":122},[33,292,294],{"class":35,"line":293},24,[33,295,247],{"class":122},[33,297,299],{"class":35,"line":298},25,[33,300,106],{"emptyLinePlaceholder":105},[33,302,304,306,308,311,313,315],{"class":35,"line":303},26,[33,305,184],{"class":159},[33,307,163],{"class":122},[33,309,310],{"class":129},"\"handles negative values\"",[33,312,169],{"class":122},[33,314,172],{"class":118},[33,316,269],{"class":122},[33,318,320,322,324,326,328,331,333,335,337,339,341,343,345],{"class":35,"line":319},27,[33,321,204],{"class":159},[33,323,163],{"class":122},[33,325,209],{"class":159},[33,327,212],{"class":122},[33,329,330],{"class":118},"-",[33,332,216],{"class":215},[33,334,219],{"class":122},[33,336,222],{"class":215},[33,338,230],{"class":122},[33,340,233],{"class":159},[33,342,163],{"class":122},[33,344,216],{"class":215},[33,346,241],{"class":122},[33,348,350],{"class":35,"line":349},28,[33,351,247],{"class":122},[33,353,355],{"class":35,"line":354},29,[33,356,357],{"class":122},"});\n",[33,359,361],{"class":35,"line":360},30,[33,362,106],{"emptyLinePlaceholder":105},[33,364,366],{"class":35,"line":365},31,[33,367,368],{"class":39},"\u002F\u002F ── Vitest assertions ──\n",[33,370,372,375,378,380,383],{"class":35,"line":371},32,[33,373,374],{"class":159},"expect",[33,376,377],{"class":122},"(value).",[33,379,233],{"class":159},[33,381,382],{"class":122},"(expected);        ",[33,384,385],{"class":39},"\u002F\u002F strict equality (===)\n",[33,387,389,391,393,396,399],{"class":35,"line":388},33,[33,390,374],{"class":159},[33,392,377],{"class":122},[33,394,395],{"class":159},"toEqual",[33,397,398],{"class":122},"(expected);    ",[33,400,401],{"class":39},"\u002F\u002F deep equality (object\u002Farray content)\n",[33,403,405,407,409,412,415],{"class":35,"line":404},34,[33,406,374],{"class":159},[33,408,377],{"class":122},[33,410,411],{"class":159},"toBeTruthy",[33,413,414],{"class":122},"();         ",[33,416,417],{"class":39},"\u002F\u002F truthy check\n",[33,419,421,423,425,428,431],{"class":35,"line":420},35,[33,422,374],{"class":159},[33,424,377],{"class":122},[33,426,427],{"class":159},"toBeFalsy",[33,429,430],{"class":122},"();          ",[33,432,433],{"class":39},"\u002F\u002F falsy check\n",[33,435,437,439,441,444,447],{"class":35,"line":436},36,[33,438,374],{"class":159},[33,440,377],{"class":122},[33,442,443],{"class":159},"toBeNull",[33,445,446],{"class":122},"();           ",[33,448,449],{"class":39},"\u002F\u002F === null\n",[33,451,453,455,457,460,463],{"class":35,"line":452},37,[33,454,374],{"class":159},[33,456,377],{"class":122},[33,458,459],{"class":159},"toBeUndefined",[33,461,462],{"class":122},"();      ",[33,464,465],{"class":39},"\u002F\u002F === undefined\n",[33,467,469,471,473,476,479],{"class":35,"line":468},38,[33,470,374],{"class":159},[33,472,377],{"class":122},[33,474,475],{"class":159},"toBeNaN",[33,477,478],{"class":122},"();            ",[33,480,481],{"class":39},"\u002F\u002F Number.isNaN\n",[33,483,485,487,490,493,496],{"class":35,"line":484},39,[33,486,374],{"class":159},[33,488,489],{"class":122},"(array).",[33,491,492],{"class":159},"toContain",[33,494,495],{"class":122},"(item);     ",[33,497,498],{"class":39},"\u002F\u002F array.includes(item) or string.includes\n",[33,500,502,504,506,509,511,514,517],{"class":35,"line":501},40,[33,503,374],{"class":159},[33,505,489],{"class":122},[33,507,508],{"class":159},"toHaveLength",[33,510,163],{"class":122},[33,512,513],{"class":215},"3",[33,515,516],{"class":122},");     ",[33,518,519],{"class":39},"\u002F\u002F array.length === 3\n",[33,521,523,525,528,531,533,536,539],{"class":35,"line":522},41,[33,524,374],{"class":159},[33,526,527],{"class":122},"(fn).",[33,529,530],{"class":159},"toThrow",[33,532,163],{"class":122},[33,534,535],{"class":129},"\"error msg\"",[33,537,538],{"class":122},");   ",[33,540,541],{"class":39},"\u002F\u002F function throws\n",[33,543,545,547,549,552,554,557,561,563,566],{"class":35,"line":544},42,[33,546,374],{"class":159},[33,548,377],{"class":122},[33,550,551],{"class":159},"toMatch",[33,553,163],{"class":122},[33,555,556],{"class":129},"\u002F",[33,558,560],{"class":559},"svAP2","regex",[33,562,556],{"class":129},[33,564,565],{"class":122},");    ",[33,567,568],{"class":39},"\u002F\u002F regex match\n",[33,570,572,574,577,580,583],{"class":35,"line":571},43,[33,573,374],{"class":159},[33,575,576],{"class":122},"(object).",[33,578,579],{"class":159},"toMatchObject",[33,581,582],{"class":122},"({ key: value });  ",[33,584,585],{"class":39},"\u002F\u002F partial object match\n",[15,587,589,590,219,593,596,597],{"id":588},"mocking-vifn-vimock-and-vispyon","Mocking: ",[30,591,592],{},"vi.fn",[30,594,595],{},"vi.mock",", and ",[30,598,599],{},"vi.spyOn",[20,601,602],{"language":22},[24,603,605],{"className":26,"code":604,"language":22,"meta":28,"style":28},"import { vi, describe, it, expect, beforeEach } from \"vitest\";\n\n\u002F\u002F ── vi.fn: create a mock function (track calls, return values) ──\nconst mockCallback = vi.fn();\nmockCallback(\"arg1\", \"arg2\");\nexpect(mockCallback).toHaveBeenCalledTimes(1);\nexpect(mockCallback).toHaveBeenCalledWith(\"arg1\", \"arg2\");\nexpect(mockCallback).toHaveBeenLastCalledWith(\"arg1\", \"arg2\");\n\n\u002F\u002F Control the return value:\nconst mockFetch = vi.fn().mockResolvedValue({ ok: true, json: () => ({ data: \"test\" }) });\nawait mockFetch(\"\u002Fapi\u002Fdata\");  \u002F\u002F returns the mocked resolved value\n\n\u002F\u002F ── vi.mock: replace a module's exports with mocks ──\nvi.mock(\".\u002Fapi.js\", () => ({\n    fetchData: vi.fn().mockResolvedValue({ id: 1, name: \"test\" }),\n    deleteUser: vi.fn().mockResolvedValue(true),\n}));\n\nimport { fetchData, deleteUser } from \".\u002Fapi.js\";\n\u002F\u002F fetchData is now a mock — returns { id: 1, name: \"test\" } without hitting the real API\n\n\u002F\u002F ── vi.spyOn: spy on an existing method (track calls, optionally override) ──\nconst obj = { method: (x) => x * 2 };\nconst spy = vi.spyOn(obj, \"method\");\nobj.method(5);\nexpect(spy).toHaveBeenCalledWith(5);\nexpect(spy).toHaveReturnedWith(10);\n\u002F\u002F spy tracks calls but calls the REAL method (unless you mock the return):\nspy.mockReturnValue(999);\nobj.method(5);  \u002F\u002F returns 999 (mocked, real method not called)\n\n\u002F\u002F ── beforeEach \u002F afterEach: setup and teardown ──\nbeforeEach(() => {\n    vi.clearAllMocks();  \u002F\u002F reset call counts and return values\n    \u002F\u002F vi.resetAllMocks();  \u002F\u002F also resets the implementation\n});\n\nafterEach(() => {\n    vi.restoreAllMocks();  \u002F\u002F restore spies to original implementations\n});\n",[30,606,607,620,624,629,649,666,683,702,721,725,730,774,792,796,801,821,845,863,868,872,886,891,895,900,939,961,975,990,1005,1010,1025,1040,1044,1049,1061,1075,1083,1087,1091,1102,1114],{"__ignoreMap":28},[33,608,609,611,614,616,618],{"class":35,"line":36},[33,610,119],{"class":118},[33,612,613],{"class":122}," { vi, describe, it, expect, beforeEach } ",[33,615,126],{"class":118},[33,617,130],{"class":129},[33,619,133],{"class":122},[33,621,622],{"class":35,"line":43},[33,623,106],{"emptyLinePlaceholder":105},[33,625,626],{"class":35,"line":49},[33,627,628],{"class":39},"\u002F\u002F ── vi.fn: create a mock function (track calls, return values) ──\n",[33,630,631,634,637,640,643,646],{"class":35,"line":55},[33,632,633],{"class":118},"const",[33,635,636],{"class":215}," mockCallback",[33,638,639],{"class":118}," =",[33,641,642],{"class":122}," vi.",[33,644,645],{"class":159},"fn",[33,647,648],{"class":122},"();\n",[33,650,651,654,656,659,661,664],{"class":35,"line":61},[33,652,653],{"class":159},"mockCallback",[33,655,163],{"class":122},[33,657,658],{"class":129},"\"arg1\"",[33,660,219],{"class":122},[33,662,663],{"class":129},"\"arg2\"",[33,665,241],{"class":122},[33,667,668,670,673,676,678,681],{"class":35,"line":67},[33,669,374],{"class":159},[33,671,672],{"class":122},"(mockCallback).",[33,674,675],{"class":159},"toHaveBeenCalledTimes",[33,677,163],{"class":122},[33,679,680],{"class":215},"1",[33,682,241],{"class":122},[33,684,685,687,689,692,694,696,698,700],{"class":35,"line":73},[33,686,374],{"class":159},[33,688,672],{"class":122},[33,690,691],{"class":159},"toHaveBeenCalledWith",[33,693,163],{"class":122},[33,695,658],{"class":129},[33,697,219],{"class":122},[33,699,663],{"class":129},[33,701,241],{"class":122},[33,703,704,706,708,711,713,715,717,719],{"class":35,"line":79},[33,705,374],{"class":159},[33,707,672],{"class":122},[33,709,710],{"class":159},"toHaveBeenLastCalledWith",[33,712,163],{"class":122},[33,714,658],{"class":129},[33,716,219],{"class":122},[33,718,663],{"class":129},[33,720,241],{"class":122},[33,722,723],{"class":35,"line":84},[33,724,106],{"emptyLinePlaceholder":105},[33,726,727],{"class":35,"line":90},[33,728,729],{"class":39},"\u002F\u002F Control the return value:\n",[33,731,732,734,737,739,741,743,746,749,752,755,757,760,763,765,768,771],{"class":35,"line":96},[33,733,633],{"class":118},[33,735,736],{"class":215}," mockFetch",[33,738,639],{"class":118},[33,740,642],{"class":122},[33,742,645],{"class":159},[33,744,745],{"class":122},"().",[33,747,748],{"class":159},"mockResolvedValue",[33,750,751],{"class":122},"({ ok: ",[33,753,754],{"class":215},"true",[33,756,219],{"class":122},[33,758,759],{"class":159},"json",[33,761,762],{"class":122},": () ",[33,764,172],{"class":118},[33,766,767],{"class":122}," ({ data: ",[33,769,770],{"class":129},"\"test\"",[33,772,773],{"class":122}," }) });\n",[33,775,776,779,781,783,786,789],{"class":35,"line":102},[33,777,778],{"class":118},"await",[33,780,736],{"class":159},[33,782,163],{"class":122},[33,784,785],{"class":129},"\"\u002Fapi\u002Fdata\"",[33,787,788],{"class":122},");  ",[33,790,791],{"class":39},"\u002F\u002F returns the mocked resolved value\n",[33,793,794],{"class":35,"line":109},[33,795,106],{"emptyLinePlaceholder":105},[33,797,798],{"class":35,"line":115},[33,799,800],{"class":39},"\u002F\u002F ── vi.mock: replace a module's exports with mocks ──\n",[33,802,803,806,809,811,814,816,818],{"class":35,"line":136},[33,804,805],{"class":122},"vi.",[33,807,808],{"class":159},"mock",[33,810,163],{"class":122},[33,812,813],{"class":129},"\".\u002Fapi.js\"",[33,815,169],{"class":122},[33,817,172],{"class":118},[33,819,820],{"class":122}," ({\n",[33,822,823,826,828,830,832,835,837,840,842],{"class":35,"line":151},[33,824,825],{"class":122},"    fetchData: vi.",[33,827,645],{"class":159},[33,829,745],{"class":122},[33,831,748],{"class":159},[33,833,834],{"class":122},"({ id: ",[33,836,680],{"class":215},[33,838,839],{"class":122},", name: ",[33,841,770],{"class":129},[33,843,844],{"class":122}," }),\n",[33,846,847,850,852,854,856,858,860],{"class":35,"line":156},[33,848,849],{"class":122},"    deleteUser: vi.",[33,851,645],{"class":159},[33,853,745],{"class":122},[33,855,748],{"class":159},[33,857,163],{"class":122},[33,859,754],{"class":215},[33,861,862],{"class":122},"),\n",[33,864,865],{"class":35,"line":181},[33,866,867],{"class":122},"}));\n",[33,869,870],{"class":35,"line":201},[33,871,106],{"emptyLinePlaceholder":105},[33,873,874,876,879,881,884],{"class":35,"line":244},[33,875,119],{"class":118},[33,877,878],{"class":122}," { fetchData, deleteUser } ",[33,880,126],{"class":118},[33,882,883],{"class":129}," \".\u002Fapi.js\"",[33,885,133],{"class":122},[33,887,888],{"class":35,"line":250},[33,889,890],{"class":39},"\u002F\u002F fetchData is now a mock — returns { id: 1, name: \"test\" } without hitting the real API\n",[33,892,893],{"class":35,"line":255},[33,894,106],{"emptyLinePlaceholder":105},[33,896,897],{"class":35,"line":272},[33,898,899],{"class":39},"\u002F\u002F ── vi.spyOn: spy on an existing method (track calls, optionally override) ──\n",[33,901,902,904,907,909,912,915,918,922,925,927,930,933,936],{"class":35,"line":293},[33,903,633],{"class":118},[33,905,906],{"class":215}," obj",[33,908,639],{"class":118},[33,910,911],{"class":122}," { ",[33,913,914],{"class":159},"method",[33,916,917],{"class":122},": (",[33,919,921],{"class":920},"sCrzJ","x",[33,923,924],{"class":122},") ",[33,926,172],{"class":118},[33,928,929],{"class":122}," x ",[33,931,932],{"class":118},"*",[33,934,935],{"class":215}," 2",[33,937,938],{"class":122}," };\n",[33,940,941,943,946,948,950,953,956,959],{"class":35,"line":298},[33,942,633],{"class":118},[33,944,945],{"class":215}," spy",[33,947,639],{"class":118},[33,949,642],{"class":122},[33,951,952],{"class":159},"spyOn",[33,954,955],{"class":122},"(obj, ",[33,957,958],{"class":129},"\"method\"",[33,960,241],{"class":122},[33,962,963,966,968,970,973],{"class":35,"line":303},[33,964,965],{"class":122},"obj.",[33,967,914],{"class":159},[33,969,163],{"class":122},[33,971,972],{"class":215},"5",[33,974,241],{"class":122},[33,976,977,979,982,984,986,988],{"class":35,"line":319},[33,978,374],{"class":159},[33,980,981],{"class":122},"(spy).",[33,983,691],{"class":159},[33,985,163],{"class":122},[33,987,972],{"class":215},[33,989,241],{"class":122},[33,991,992,994,996,999,1001,1003],{"class":35,"line":349},[33,993,374],{"class":159},[33,995,981],{"class":122},[33,997,998],{"class":159},"toHaveReturnedWith",[33,1000,163],{"class":122},[33,1002,216],{"class":215},[33,1004,241],{"class":122},[33,1006,1007],{"class":35,"line":354},[33,1008,1009],{"class":39},"\u002F\u002F spy tracks calls but calls the REAL method (unless you mock the return):\n",[33,1011,1012,1015,1018,1020,1023],{"class":35,"line":360},[33,1013,1014],{"class":122},"spy.",[33,1016,1017],{"class":159},"mockReturnValue",[33,1019,163],{"class":122},[33,1021,1022],{"class":215},"999",[33,1024,241],{"class":122},[33,1026,1027,1029,1031,1033,1035,1037],{"class":35,"line":365},[33,1028,965],{"class":122},[33,1030,914],{"class":159},[33,1032,163],{"class":122},[33,1034,972],{"class":215},[33,1036,788],{"class":122},[33,1038,1039],{"class":39},"\u002F\u002F returns 999 (mocked, real method not called)\n",[33,1041,1042],{"class":35,"line":371},[33,1043,106],{"emptyLinePlaceholder":105},[33,1045,1046],{"class":35,"line":388},[33,1047,1048],{"class":39},"\u002F\u002F ── beforeEach \u002F afterEach: setup and teardown ──\n",[33,1050,1051,1054,1057,1059],{"class":35,"line":404},[33,1052,1053],{"class":159},"beforeEach",[33,1055,1056],{"class":122},"(() ",[33,1058,172],{"class":118},[33,1060,269],{"class":122},[33,1062,1063,1066,1069,1072],{"class":35,"line":420},[33,1064,1065],{"class":122},"    vi.",[33,1067,1068],{"class":159},"clearAllMocks",[33,1070,1071],{"class":122},"();  ",[33,1073,1074],{"class":39},"\u002F\u002F reset call counts and return values\n",[33,1076,1077,1080],{"class":35,"line":436},[33,1078,1079],{"class":39},"    \u002F\u002F vi.resetAllMocks();",[33,1081,1082],{"class":39},"  \u002F\u002F also resets the implementation\n",[33,1084,1085],{"class":35,"line":452},[33,1086,357],{"class":122},[33,1088,1089],{"class":35,"line":468},[33,1090,106],{"emptyLinePlaceholder":105},[33,1092,1093,1096,1098,1100],{"class":35,"line":484},[33,1094,1095],{"class":159},"afterEach",[33,1097,1056],{"class":122},[33,1099,172],{"class":118},[33,1101,269],{"class":122},[33,1103,1104,1106,1109,1111],{"class":35,"line":501},[33,1105,1065],{"class":122},[33,1107,1108],{"class":159},"restoreAllMocks",[33,1110,1071],{"class":122},[33,1112,1113],{"class":39},"\u002F\u002F restore spies to original implementations\n",[33,1115,1116],{"class":35,"line":522},[33,1117,357],{"class":122},[15,1119,1121],{"id":1120},"anti-pattern-testing-implementation-details","Anti-Pattern: Testing Implementation Details",[20,1123,1124],{"language":22},[24,1125,1127],{"className":26,"code":1126,"language":22,"meta":28,"style":28},"\u002F\u002F ❌ NAIVE — testing implementation details (breaks on refactoring)\ndescribe(\"UserService\", () => {\n    it(\"uses Map internally\", () => {\n        const service = new UserService();\n        expect(service.users).toBeInstanceOf(Map);  \u002F\u002F testing the internal data structure\n        \u002F\u002F If you refactor from Map to Array, this test breaks — even if behavior is correct.\n    });\n\n    it(\"calls validate exactly once\", () => {\n        const spy = vi.spyOn(service, \"validate\");\n        service.create({ name: \"Alice\" });\n        expect(spy).toHaveBeenCalledTimes(1);  \u002F\u002F testing internal call count\n        \u002F\u002F If you add caching to validate, this test breaks — even if behavior is correct.\n    });\n});\n\n\u002F\u002F ✅ CORRECT — test behavior (public API, not internals)\ndescribe(\"UserService\", () => {\n    it(\"creates a user and returns it with an ID\", () => {\n        const service = new UserService();\n        const user = service.create({ name: \"Alice\" });\n        expect(user).toEqual(expect.objectContaining({  \u002F\u002F partial match\n            id: expect.any(Number),\n            name: \"Alice\",\n        }));\n    });\n\n    it(\"throws on invalid input\", () => {\n        const service = new UserService();\n        expect(() => service.create({ name: \"\" })).toThrow(\"invalid name\");\n        expect(() => service.create({})).toThrow(\"invalid name\");\n    });\n\n    it(\"retrieves a created user by ID\", () => {\n        const service = new UserService();\n        const created = service.create({ name: \"Alice\" });\n        const found = service.getById(created.id);\n        expect(found).toEqual(created);\n    });\n});\n",[30,1128,1129,1134,1149,1164,1182,1198,1203,1207,1211,1226,1246,1263,1280,1285,1289,1293,1297,1302,1316,1331,1345,1365,1386,1397,1407,1412,1416,1420,1435,1449,1478,1501,1505,1509,1524,1538,1557,1574,1586,1590],{"__ignoreMap":28},[33,1130,1131],{"class":35,"line":36},[33,1132,1133],{"class":39},"\u002F\u002F ❌ NAIVE — testing implementation details (breaks on refactoring)\n",[33,1135,1136,1138,1140,1143,1145,1147],{"class":35,"line":43},[33,1137,160],{"class":159},[33,1139,163],{"class":122},[33,1141,1142],{"class":129},"\"UserService\"",[33,1144,169],{"class":122},[33,1146,172],{"class":118},[33,1148,269],{"class":122},[33,1150,1151,1153,1155,1158,1160,1162],{"class":35,"line":49},[33,1152,184],{"class":159},[33,1154,163],{"class":122},[33,1156,1157],{"class":129},"\"uses Map internally\"",[33,1159,169],{"class":122},[33,1161,172],{"class":118},[33,1163,269],{"class":122},[33,1165,1166,1169,1172,1174,1177,1180],{"class":35,"line":55},[33,1167,1168],{"class":118},"        const",[33,1170,1171],{"class":215}," service",[33,1173,639],{"class":118},[33,1175,1176],{"class":118}," new",[33,1178,1179],{"class":159}," UserService",[33,1181,648],{"class":122},[33,1183,1184,1186,1189,1192,1195],{"class":35,"line":61},[33,1185,204],{"class":159},[33,1187,1188],{"class":122},"(service.users).",[33,1190,1191],{"class":159},"toBeInstanceOf",[33,1193,1194],{"class":122},"(Map);  ",[33,1196,1197],{"class":39},"\u002F\u002F testing the internal data structure\n",[33,1199,1200],{"class":35,"line":67},[33,1201,1202],{"class":39},"        \u002F\u002F If you refactor from Map to Array, this test breaks — even if behavior is correct.\n",[33,1204,1205],{"class":35,"line":73},[33,1206,247],{"class":122},[33,1208,1209],{"class":35,"line":79},[33,1210,106],{"emptyLinePlaceholder":105},[33,1212,1213,1215,1217,1220,1222,1224],{"class":35,"line":84},[33,1214,184],{"class":159},[33,1216,163],{"class":122},[33,1218,1219],{"class":129},"\"calls validate exactly once\"",[33,1221,169],{"class":122},[33,1223,172],{"class":118},[33,1225,269],{"class":122},[33,1227,1228,1230,1232,1234,1236,1238,1241,1244],{"class":35,"line":90},[33,1229,1168],{"class":118},[33,1231,945],{"class":215},[33,1233,639],{"class":118},[33,1235,642],{"class":122},[33,1237,952],{"class":159},[33,1239,1240],{"class":122},"(service, ",[33,1242,1243],{"class":129},"\"validate\"",[33,1245,241],{"class":122},[33,1247,1248,1251,1254,1257,1260],{"class":35,"line":96},[33,1249,1250],{"class":122},"        service.",[33,1252,1253],{"class":159},"create",[33,1255,1256],{"class":122},"({ name: ",[33,1258,1259],{"class":129},"\"Alice\"",[33,1261,1262],{"class":122}," });\n",[33,1264,1265,1267,1269,1271,1273,1275,1277],{"class":35,"line":102},[33,1266,204],{"class":159},[33,1268,981],{"class":122},[33,1270,675],{"class":159},[33,1272,163],{"class":122},[33,1274,680],{"class":215},[33,1276,788],{"class":122},[33,1278,1279],{"class":39},"\u002F\u002F testing internal call count\n",[33,1281,1282],{"class":35,"line":109},[33,1283,1284],{"class":39},"        \u002F\u002F If you add caching to validate, this test breaks — even if behavior is correct.\n",[33,1286,1287],{"class":35,"line":115},[33,1288,247],{"class":122},[33,1290,1291],{"class":35,"line":136},[33,1292,357],{"class":122},[33,1294,1295],{"class":35,"line":151},[33,1296,106],{"emptyLinePlaceholder":105},[33,1298,1299],{"class":35,"line":156},[33,1300,1301],{"class":39},"\u002F\u002F ✅ CORRECT — test behavior (public API, not internals)\n",[33,1303,1304,1306,1308,1310,1312,1314],{"class":35,"line":181},[33,1305,160],{"class":159},[33,1307,163],{"class":122},[33,1309,1142],{"class":129},[33,1311,169],{"class":122},[33,1313,172],{"class":118},[33,1315,269],{"class":122},[33,1317,1318,1320,1322,1325,1327,1329],{"class":35,"line":201},[33,1319,184],{"class":159},[33,1321,163],{"class":122},[33,1323,1324],{"class":129},"\"creates a user and returns it with an ID\"",[33,1326,169],{"class":122},[33,1328,172],{"class":118},[33,1330,269],{"class":122},[33,1332,1333,1335,1337,1339,1341,1343],{"class":35,"line":244},[33,1334,1168],{"class":118},[33,1336,1171],{"class":215},[33,1338,639],{"class":118},[33,1340,1176],{"class":118},[33,1342,1179],{"class":159},[33,1344,648],{"class":122},[33,1346,1347,1349,1352,1354,1357,1359,1361,1363],{"class":35,"line":250},[33,1348,1168],{"class":118},[33,1350,1351],{"class":215}," user",[33,1353,639],{"class":118},[33,1355,1356],{"class":122}," service.",[33,1358,1253],{"class":159},[33,1360,1256],{"class":122},[33,1362,1259],{"class":129},[33,1364,1262],{"class":122},[33,1366,1367,1369,1372,1374,1377,1380,1383],{"class":35,"line":255},[33,1368,204],{"class":159},[33,1370,1371],{"class":122},"(user).",[33,1373,395],{"class":159},[33,1375,1376],{"class":122},"(expect.",[33,1378,1379],{"class":159},"objectContaining",[33,1381,1382],{"class":122},"({  ",[33,1384,1385],{"class":39},"\u002F\u002F partial match\n",[33,1387,1388,1391,1394],{"class":35,"line":272},[33,1389,1390],{"class":122},"            id: expect.",[33,1392,1393],{"class":159},"any",[33,1395,1396],{"class":122},"(Number),\n",[33,1398,1399,1402,1404],{"class":35,"line":293},[33,1400,1401],{"class":122},"            name: ",[33,1403,1259],{"class":129},[33,1405,1406],{"class":122},",\n",[33,1408,1409],{"class":35,"line":298},[33,1410,1411],{"class":122},"        }));\n",[33,1413,1414],{"class":35,"line":303},[33,1415,247],{"class":122},[33,1417,1418],{"class":35,"line":319},[33,1419,106],{"emptyLinePlaceholder":105},[33,1421,1422,1424,1426,1429,1431,1433],{"class":35,"line":349},[33,1423,184],{"class":159},[33,1425,163],{"class":122},[33,1427,1428],{"class":129},"\"throws on invalid input\"",[33,1430,169],{"class":122},[33,1432,172],{"class":118},[33,1434,269],{"class":122},[33,1436,1437,1439,1441,1443,1445,1447],{"class":35,"line":354},[33,1438,1168],{"class":118},[33,1440,1171],{"class":215},[33,1442,639],{"class":118},[33,1444,1176],{"class":118},[33,1446,1179],{"class":159},[33,1448,648],{"class":122},[33,1450,1451,1453,1455,1457,1459,1461,1463,1466,1469,1471,1473,1476],{"class":35,"line":360},[33,1452,204],{"class":159},[33,1454,1056],{"class":122},[33,1456,172],{"class":118},[33,1458,1356],{"class":122},[33,1460,1253],{"class":159},[33,1462,1256],{"class":122},[33,1464,1465],{"class":129},"\"\"",[33,1467,1468],{"class":122}," })).",[33,1470,530],{"class":159},[33,1472,163],{"class":122},[33,1474,1475],{"class":129},"\"invalid name\"",[33,1477,241],{"class":122},[33,1479,1480,1482,1484,1486,1488,1490,1493,1495,1497,1499],{"class":35,"line":365},[33,1481,204],{"class":159},[33,1483,1056],{"class":122},[33,1485,172],{"class":118},[33,1487,1356],{"class":122},[33,1489,1253],{"class":159},[33,1491,1492],{"class":122},"({})).",[33,1494,530],{"class":159},[33,1496,163],{"class":122},[33,1498,1475],{"class":129},[33,1500,241],{"class":122},[33,1502,1503],{"class":35,"line":371},[33,1504,247],{"class":122},[33,1506,1507],{"class":35,"line":388},[33,1508,106],{"emptyLinePlaceholder":105},[33,1510,1511,1513,1515,1518,1520,1522],{"class":35,"line":404},[33,1512,184],{"class":159},[33,1514,163],{"class":122},[33,1516,1517],{"class":129},"\"retrieves a created user by ID\"",[33,1519,169],{"class":122},[33,1521,172],{"class":118},[33,1523,269],{"class":122},[33,1525,1526,1528,1530,1532,1534,1536],{"class":35,"line":420},[33,1527,1168],{"class":118},[33,1529,1171],{"class":215},[33,1531,639],{"class":118},[33,1533,1176],{"class":118},[33,1535,1179],{"class":159},[33,1537,648],{"class":122},[33,1539,1540,1542,1545,1547,1549,1551,1553,1555],{"class":35,"line":436},[33,1541,1168],{"class":118},[33,1543,1544],{"class":215}," created",[33,1546,639],{"class":118},[33,1548,1356],{"class":122},[33,1550,1253],{"class":159},[33,1552,1256],{"class":122},[33,1554,1259],{"class":129},[33,1556,1262],{"class":122},[33,1558,1559,1561,1564,1566,1568,1571],{"class":35,"line":452},[33,1560,1168],{"class":118},[33,1562,1563],{"class":215}," found",[33,1565,639],{"class":118},[33,1567,1356],{"class":122},[33,1569,1570],{"class":159},"getById",[33,1572,1573],{"class":122},"(created.id);\n",[33,1575,1576,1578,1581,1583],{"class":35,"line":468},[33,1577,204],{"class":159},[33,1579,1580],{"class":122},"(found).",[33,1582,395],{"class":159},[33,1584,1585],{"class":122},"(created);\n",[33,1587,1588],{"class":35,"line":484},[33,1589,247],{"class":122},[33,1591,1592],{"class":35,"line":501},[33,1593,357],{"class":122},[15,1595,1597],{"id":1596},"production-pattern-dependency-injection-for-testability","Production Pattern: Dependency Injection for Testability",[20,1599,1600],{"language":22},[24,1601,1603],{"className":26,"code":1602,"language":22,"meta":28,"style":28},"\u002F\u002F ── Inject dependencies (don't import them directly) for easy mocking ──\n\n\u002F\u002F ❌ Hard to test (direct import — can't mock without vi.mock)\nclass UserService {\n    async getUser(id) {\n        const response = await fetch(`\u002Fapi\u002Fusers\u002F${id}`);  \u002F\u002F direct fetch — hard to mock\n        return response.json();\n    }\n}\n\n\u002F\u002F ✅ Testable (dependency injection — pass fetch as a constructor arg)\nclass TestableUserService {\n    constructor(fetchImpl = fetch) {  \u002F\u002F default to global fetch, but injectable\n        this.fetch = fetchImpl;\n    }\n    async getUser(id) {\n        const response = await this.fetch(`\u002Fapi\u002Fusers\u002F${id}`);\n        return response.json();\n    }\n}\n\n\u002F\u002F Test with a mock fetch:\ntest(\"getUser fetches from the API\", async () => {\n    const mockFetch = vi.fn().mockResolvedValue({\n        json: () => Promise.resolve({ id: 1, name: \"Alice\" }),\n    });\n    const service = new TestableUserService(mockFetch);\n\n    const user = await service.getUser(1);\n\n    expect(mockFetch).toHaveBeenCalledWith(\"\u002Fapi\u002Fusers\u002F1\");\n    expect(user).toEqual({ id: 1, name: \"Alice\" });\n});\n\n\u002F\u002F ── Factory function with injected dependencies ──\nconst createUserService = ({ fetch: fetchImpl = fetch, log = console.log } = {}) => ({\n    async getUser(id) {\n        const response = await fetchImpl(`\u002Fapi\u002Fusers\u002F${id}`);\n        const user = await response.json();\n        log(\"fetched user:\", user.name);\n        return user;\n    },\n});\n\u002F\u002F Test: createUserService({ fetch: mockFetch, log: mockLog })\n",[30,1604,1605,1610,1614,1619,1628,1644,1674,1686,1691,1696,1700,1705,1714,1732,1746,1750,1762,1791,1801,1805,1809,1813,1818,1840,1860,1887,1891,1906,1910,1931,1935,1952,1970,1974,1978,1983,2024,2036,2059,2075,2088,2095,2100,2104],{"__ignoreMap":28},[33,1606,1607],{"class":35,"line":36},[33,1608,1609],{"class":39},"\u002F\u002F ── Inject dependencies (don't import them directly) for easy mocking ──\n",[33,1611,1612],{"class":35,"line":43},[33,1613,106],{"emptyLinePlaceholder":105},[33,1615,1616],{"class":35,"line":49},[33,1617,1618],{"class":39},"\u002F\u002F ❌ Hard to test (direct import — can't mock without vi.mock)\n",[33,1620,1621,1624,1626],{"class":35,"line":55},[33,1622,1623],{"class":118},"class",[33,1625,1179],{"class":159},[33,1627,269],{"class":122},[33,1629,1630,1633,1636,1638,1641],{"class":35,"line":61},[33,1631,1632],{"class":118},"    async",[33,1634,1635],{"class":159}," getUser",[33,1637,163],{"class":122},[33,1639,1640],{"class":920},"id",[33,1642,1643],{"class":122},") {\n",[33,1645,1646,1648,1651,1653,1656,1659,1661,1664,1666,1669,1671],{"class":35,"line":67},[33,1647,1168],{"class":118},[33,1649,1650],{"class":215}," response",[33,1652,639],{"class":118},[33,1654,1655],{"class":118}," await",[33,1657,1658],{"class":159}," fetch",[33,1660,163],{"class":122},[33,1662,1663],{"class":129},"`\u002Fapi\u002Fusers\u002F${",[33,1665,1640],{"class":122},[33,1667,1668],{"class":129},"}`",[33,1670,788],{"class":122},[33,1672,1673],{"class":39},"\u002F\u002F direct fetch — hard to mock\n",[33,1675,1676,1679,1682,1684],{"class":35,"line":73},[33,1677,1678],{"class":118},"        return",[33,1680,1681],{"class":122}," response.",[33,1683,759],{"class":159},[33,1685,648],{"class":122},[33,1687,1688],{"class":35,"line":79},[33,1689,1690],{"class":122},"    }\n",[33,1692,1693],{"class":35,"line":84},[33,1694,1695],{"class":122},"}\n",[33,1697,1698],{"class":35,"line":90},[33,1699,106],{"emptyLinePlaceholder":105},[33,1701,1702],{"class":35,"line":96},[33,1703,1704],{"class":39},"\u002F\u002F ✅ Testable (dependency injection — pass fetch as a constructor arg)\n",[33,1706,1707,1709,1712],{"class":35,"line":102},[33,1708,1623],{"class":118},[33,1710,1711],{"class":159}," TestableUserService",[33,1713,269],{"class":122},[33,1715,1716,1719,1721,1724,1726,1729],{"class":35,"line":109},[33,1717,1718],{"class":118},"    constructor",[33,1720,163],{"class":122},[33,1722,1723],{"class":920},"fetchImpl",[33,1725,639],{"class":118},[33,1727,1728],{"class":122}," fetch) {  ",[33,1730,1731],{"class":39},"\u002F\u002F default to global fetch, but injectable\n",[33,1733,1734,1737,1740,1743],{"class":35,"line":115},[33,1735,1736],{"class":215},"        this",[33,1738,1739],{"class":122},".fetch ",[33,1741,1742],{"class":118},"=",[33,1744,1745],{"class":122}," fetchImpl;\n",[33,1747,1748],{"class":35,"line":136},[33,1749,1690],{"class":122},[33,1751,1752,1754,1756,1758,1760],{"class":35,"line":151},[33,1753,1632],{"class":118},[33,1755,1635],{"class":159},[33,1757,163],{"class":122},[33,1759,1640],{"class":920},[33,1761,1643],{"class":122},[33,1763,1764,1766,1768,1770,1772,1775,1778,1781,1783,1785,1787,1789],{"class":35,"line":156},[33,1765,1168],{"class":118},[33,1767,1650],{"class":215},[33,1769,639],{"class":118},[33,1771,1655],{"class":118},[33,1773,1774],{"class":215}," this",[33,1776,1777],{"class":122},".",[33,1779,1780],{"class":159},"fetch",[33,1782,163],{"class":122},[33,1784,1663],{"class":129},[33,1786,1640],{"class":122},[33,1788,1668],{"class":129},[33,1790,241],{"class":122},[33,1792,1793,1795,1797,1799],{"class":35,"line":181},[33,1794,1678],{"class":118},[33,1796,1681],{"class":122},[33,1798,759],{"class":159},[33,1800,648],{"class":122},[33,1802,1803],{"class":35,"line":201},[33,1804,1690],{"class":122},[33,1806,1807],{"class":35,"line":244},[33,1808,1695],{"class":122},[33,1810,1811],{"class":35,"line":250},[33,1812,106],{"emptyLinePlaceholder":105},[33,1814,1815],{"class":35,"line":255},[33,1816,1817],{"class":39},"\u002F\u002F Test with a mock fetch:\n",[33,1819,1820,1823,1825,1828,1830,1833,1836,1838],{"class":35,"line":272},[33,1821,1822],{"class":159},"test",[33,1824,163],{"class":122},[33,1826,1827],{"class":129},"\"getUser fetches from the API\"",[33,1829,219],{"class":122},[33,1831,1832],{"class":118},"async",[33,1834,1835],{"class":122}," () ",[33,1837,172],{"class":118},[33,1839,269],{"class":122},[33,1841,1842,1845,1847,1849,1851,1853,1855,1857],{"class":35,"line":293},[33,1843,1844],{"class":118},"    const",[33,1846,736],{"class":215},[33,1848,639],{"class":118},[33,1850,642],{"class":122},[33,1852,645],{"class":159},[33,1854,745],{"class":122},[33,1856,748],{"class":159},[33,1858,1859],{"class":122},"({\n",[33,1861,1862,1865,1867,1869,1872,1874,1877,1879,1881,1883,1885],{"class":35,"line":298},[33,1863,1864],{"class":159},"        json",[33,1866,762],{"class":122},[33,1868,172],{"class":118},[33,1870,1871],{"class":215}," Promise",[33,1873,1777],{"class":122},[33,1875,1876],{"class":159},"resolve",[33,1878,834],{"class":122},[33,1880,680],{"class":215},[33,1882,839],{"class":122},[33,1884,1259],{"class":129},[33,1886,844],{"class":122},[33,1888,1889],{"class":35,"line":303},[33,1890,247],{"class":122},[33,1892,1893,1895,1897,1899,1901,1903],{"class":35,"line":319},[33,1894,1844],{"class":118},[33,1896,1171],{"class":215},[33,1898,639],{"class":118},[33,1900,1176],{"class":118},[33,1902,1711],{"class":159},[33,1904,1905],{"class":122},"(mockFetch);\n",[33,1907,1908],{"class":35,"line":349},[33,1909,106],{"emptyLinePlaceholder":105},[33,1911,1912,1914,1916,1918,1920,1922,1925,1927,1929],{"class":35,"line":354},[33,1913,1844],{"class":118},[33,1915,1351],{"class":215},[33,1917,639],{"class":118},[33,1919,1655],{"class":118},[33,1921,1356],{"class":122},[33,1923,1924],{"class":159},"getUser",[33,1926,163],{"class":122},[33,1928,680],{"class":215},[33,1930,241],{"class":122},[33,1932,1933],{"class":35,"line":360},[33,1934,106],{"emptyLinePlaceholder":105},[33,1936,1937,1940,1943,1945,1947,1950],{"class":35,"line":365},[33,1938,1939],{"class":159},"    expect",[33,1941,1942],{"class":122},"(mockFetch).",[33,1944,691],{"class":159},[33,1946,163],{"class":122},[33,1948,1949],{"class":129},"\"\u002Fapi\u002Fusers\u002F1\"",[33,1951,241],{"class":122},[33,1953,1954,1956,1958,1960,1962,1964,1966,1968],{"class":35,"line":371},[33,1955,1939],{"class":159},[33,1957,1371],{"class":122},[33,1959,395],{"class":159},[33,1961,834],{"class":122},[33,1963,680],{"class":215},[33,1965,839],{"class":122},[33,1967,1259],{"class":129},[33,1969,1262],{"class":122},[33,1971,1972],{"class":35,"line":388},[33,1973,357],{"class":122},[33,1975,1976],{"class":35,"line":404},[33,1977,106],{"emptyLinePlaceholder":105},[33,1979,1980],{"class":35,"line":420},[33,1981,1982],{"class":39},"\u002F\u002F ── Factory function with injected dependencies ──\n",[33,1984,1985,1987,1990,1992,1995,1997,2000,2002,2004,2007,2010,2012,2015,2017,2020,2022],{"class":35,"line":436},[33,1986,633],{"class":118},[33,1988,1989],{"class":159}," createUserService",[33,1991,639],{"class":118},[33,1993,1994],{"class":122}," ({ ",[33,1996,1780],{"class":920},[33,1998,1999],{"class":122},": ",[33,2001,1723],{"class":920},[33,2003,639],{"class":118},[33,2005,2006],{"class":122}," fetch, ",[33,2008,2009],{"class":920},"log",[33,2011,639],{"class":118},[33,2013,2014],{"class":122}," console.log } ",[33,2016,1742],{"class":118},[33,2018,2019],{"class":122}," {}) ",[33,2021,172],{"class":118},[33,2023,820],{"class":122},[33,2025,2026,2028,2030,2032,2034],{"class":35,"line":452},[33,2027,1632],{"class":118},[33,2029,1635],{"class":159},[33,2031,163],{"class":122},[33,2033,1640],{"class":920},[33,2035,1643],{"class":122},[33,2037,2038,2040,2042,2044,2046,2049,2051,2053,2055,2057],{"class":35,"line":468},[33,2039,1168],{"class":118},[33,2041,1650],{"class":215},[33,2043,639],{"class":118},[33,2045,1655],{"class":118},[33,2047,2048],{"class":159}," fetchImpl",[33,2050,163],{"class":122},[33,2052,1663],{"class":129},[33,2054,1640],{"class":122},[33,2056,1668],{"class":129},[33,2058,241],{"class":122},[33,2060,2061,2063,2065,2067,2069,2071,2073],{"class":35,"line":484},[33,2062,1168],{"class":118},[33,2064,1351],{"class":215},[33,2066,639],{"class":118},[33,2068,1655],{"class":118},[33,2070,1681],{"class":122},[33,2072,759],{"class":159},[33,2074,648],{"class":122},[33,2076,2077,2080,2082,2085],{"class":35,"line":501},[33,2078,2079],{"class":159},"        log",[33,2081,163],{"class":122},[33,2083,2084],{"class":129},"\"fetched user:\"",[33,2086,2087],{"class":122},", user.name);\n",[33,2089,2090,2092],{"class":35,"line":522},[33,2091,1678],{"class":118},[33,2093,2094],{"class":122}," user;\n",[33,2096,2097],{"class":35,"line":544},[33,2098,2099],{"class":122},"    },\n",[33,2101,2102],{"class":35,"line":571},[33,2103,357],{"class":122},[33,2105,2107],{"class":35,"line":2106},44,[33,2108,2109],{"class":39},"\u002F\u002F Test: createUserService({ fetch: mockFetch, log: mockLog })\n",[15,2111,2113],{"id":2112},"tdd-red-green-refactor","TDD: Red-Green-Refactor",[20,2115,2116],{"language":22},[24,2117,2119],{"className":26,"code":2118,"language":22,"meta":28,"style":28},"\u002F\u002F ── TDD cycle: 1. Red (write failing test) → 2. Green (make it pass) → 3. Refactor ──\n\n\u002F\u002F Step 1: RED — write a test for a function that doesn't exist yet\ndescribe(\"formatCurrency\", () => {\n    it(\"formats a number as USD\", () => {\n        expect(formatCurrency(1234.56)).toBe(\"$1,234.56\");\n    });\n    it(\"handles zero\", () => {\n        expect(formatCurrency(0)).toBe(\"$0.00\");\n    });\n    it(\"handles negative numbers\", () => {\n        expect(formatCurrency(-100)).toBe(\"-$100.00\");\n    });\n});\n\u002F\u002F Test fails: formatCurrency is not defined (RED — write minimal code to pass)\n\n\u002F\u002F Step 2: GREEN — write the minimum code to make the test pass\nfunction formatCurrency(amount) {\n    return new Intl.NumberFormat(\"en-US\", {\n        style: \"currency\",\n        currency: \"USD\",\n    }).format(amount);\n}\n\u002F\u002F Test passes (GREEN)\n\n\u002F\u002F Step 3: REFACTOR — improve the code without changing behavior (tests still pass)\n\u002F\u002F e.g., add error handling, optimize, extract constants — tests ensure correctness.\n\n\u002F\u002F ── Snapshot testing (for stable output like rendered HTML) ──\nit(\"matches the snapshot\", () => {\n    const html = renderComponent({ title: \"Hello\" });\n    expect(html).toMatchInlineSnapshot(`\n      \"\u003Cdiv class=\\\\\"title\\\\\">Hello\u003C\u002Fdiv>\"\n    `\");\n    \u002F\u002F If the output changes, the snapshot test fails (review and update if intentional).\n});\n",[30,2120,2121,2126,2130,2135,2150,2165,2191,2195,2210,2233,2237,2252,2278,2282,2286,2291,2295,2300,2315,2336,2346,2356,2367,2371,2376,2380,2385,2390,2394,2399,2415,2435,2450,2466,2474,2479],{"__ignoreMap":28},[33,2122,2123],{"class":35,"line":36},[33,2124,2125],{"class":39},"\u002F\u002F ── TDD cycle: 1. Red (write failing test) → 2. Green (make it pass) → 3. Refactor ──\n",[33,2127,2128],{"class":35,"line":43},[33,2129,106],{"emptyLinePlaceholder":105},[33,2131,2132],{"class":35,"line":49},[33,2133,2134],{"class":39},"\u002F\u002F Step 1: RED — write a test for a function that doesn't exist yet\n",[33,2136,2137,2139,2141,2144,2146,2148],{"class":35,"line":55},[33,2138,160],{"class":159},[33,2140,163],{"class":122},[33,2142,2143],{"class":129},"\"formatCurrency\"",[33,2145,169],{"class":122},[33,2147,172],{"class":118},[33,2149,269],{"class":122},[33,2151,2152,2154,2156,2159,2161,2163],{"class":35,"line":61},[33,2153,184],{"class":159},[33,2155,163],{"class":122},[33,2157,2158],{"class":129},"\"formats a number as USD\"",[33,2160,169],{"class":122},[33,2162,172],{"class":118},[33,2164,269],{"class":122},[33,2166,2167,2169,2171,2174,2176,2179,2182,2184,2186,2189],{"class":35,"line":67},[33,2168,204],{"class":159},[33,2170,163],{"class":122},[33,2172,2173],{"class":159},"formatCurrency",[33,2175,163],{"class":122},[33,2177,2178],{"class":215},"1234.56",[33,2180,2181],{"class":122},")).",[33,2183,233],{"class":159},[33,2185,163],{"class":122},[33,2187,2188],{"class":129},"\"$1,234.56\"",[33,2190,241],{"class":122},[33,2192,2193],{"class":35,"line":73},[33,2194,247],{"class":122},[33,2196,2197,2199,2201,2204,2206,2208],{"class":35,"line":79},[33,2198,184],{"class":159},[33,2200,163],{"class":122},[33,2202,2203],{"class":129},"\"handles zero\"",[33,2205,169],{"class":122},[33,2207,172],{"class":118},[33,2209,269],{"class":122},[33,2211,2212,2214,2216,2218,2220,2222,2224,2226,2228,2231],{"class":35,"line":84},[33,2213,204],{"class":159},[33,2215,163],{"class":122},[33,2217,2173],{"class":159},[33,2219,163],{"class":122},[33,2221,288],{"class":215},[33,2223,2181],{"class":122},[33,2225,233],{"class":159},[33,2227,163],{"class":122},[33,2229,2230],{"class":129},"\"$0.00\"",[33,2232,241],{"class":122},[33,2234,2235],{"class":35,"line":90},[33,2236,247],{"class":122},[33,2238,2239,2241,2243,2246,2248,2250],{"class":35,"line":96},[33,2240,184],{"class":159},[33,2242,163],{"class":122},[33,2244,2245],{"class":129},"\"handles negative numbers\"",[33,2247,169],{"class":122},[33,2249,172],{"class":118},[33,2251,269],{"class":122},[33,2253,2254,2256,2258,2260,2262,2264,2267,2269,2271,2273,2276],{"class":35,"line":102},[33,2255,204],{"class":159},[33,2257,163],{"class":122},[33,2259,2173],{"class":159},[33,2261,163],{"class":122},[33,2263,330],{"class":118},[33,2265,2266],{"class":215},"100",[33,2268,2181],{"class":122},[33,2270,233],{"class":159},[33,2272,163],{"class":122},[33,2274,2275],{"class":129},"\"-$100.00\"",[33,2277,241],{"class":122},[33,2279,2280],{"class":35,"line":109},[33,2281,247],{"class":122},[33,2283,2284],{"class":35,"line":115},[33,2285,357],{"class":122},[33,2287,2288],{"class":35,"line":136},[33,2289,2290],{"class":39},"\u002F\u002F Test fails: formatCurrency is not defined (RED — write minimal code to pass)\n",[33,2292,2293],{"class":35,"line":151},[33,2294,106],{"emptyLinePlaceholder":105},[33,2296,2297],{"class":35,"line":156},[33,2298,2299],{"class":39},"\u002F\u002F Step 2: GREEN — write the minimum code to make the test pass\n",[33,2301,2302,2305,2308,2310,2313],{"class":35,"line":181},[33,2303,2304],{"class":118},"function",[33,2306,2307],{"class":159}," formatCurrency",[33,2309,163],{"class":122},[33,2311,2312],{"class":920},"amount",[33,2314,1643],{"class":122},[33,2316,2317,2320,2322,2325,2328,2330,2333],{"class":35,"line":201},[33,2318,2319],{"class":118},"    return",[33,2321,1176],{"class":118},[33,2323,2324],{"class":122}," Intl.",[33,2326,2327],{"class":159},"NumberFormat",[33,2329,163],{"class":122},[33,2331,2332],{"class":129},"\"en-US\"",[33,2334,2335],{"class":122},", {\n",[33,2337,2338,2341,2344],{"class":35,"line":244},[33,2339,2340],{"class":122},"        style: ",[33,2342,2343],{"class":129},"\"currency\"",[33,2345,1406],{"class":122},[33,2347,2348,2351,2354],{"class":35,"line":250},[33,2349,2350],{"class":122},"        currency: ",[33,2352,2353],{"class":129},"\"USD\"",[33,2355,1406],{"class":122},[33,2357,2358,2361,2364],{"class":35,"line":255},[33,2359,2360],{"class":122},"    }).",[33,2362,2363],{"class":159},"format",[33,2365,2366],{"class":122},"(amount);\n",[33,2368,2369],{"class":35,"line":272},[33,2370,1695],{"class":122},[33,2372,2373],{"class":35,"line":293},[33,2374,2375],{"class":39},"\u002F\u002F Test passes (GREEN)\n",[33,2377,2378],{"class":35,"line":298},[33,2379,106],{"emptyLinePlaceholder":105},[33,2381,2382],{"class":35,"line":303},[33,2383,2384],{"class":39},"\u002F\u002F Step 3: REFACTOR — improve the code without changing behavior (tests still pass)\n",[33,2386,2387],{"class":35,"line":319},[33,2388,2389],{"class":39},"\u002F\u002F e.g., add error handling, optimize, extract constants — tests ensure correctness.\n",[33,2391,2392],{"class":35,"line":349},[33,2393,106],{"emptyLinePlaceholder":105},[33,2395,2396],{"class":35,"line":354},[33,2397,2398],{"class":39},"\u002F\u002F ── Snapshot testing (for stable output like rendered HTML) ──\n",[33,2400,2401,2404,2406,2409,2411,2413],{"class":35,"line":360},[33,2402,2403],{"class":159},"it",[33,2405,163],{"class":122},[33,2407,2408],{"class":129},"\"matches the snapshot\"",[33,2410,169],{"class":122},[33,2412,172],{"class":118},[33,2414,269],{"class":122},[33,2416,2417,2419,2422,2424,2427,2430,2433],{"class":35,"line":365},[33,2418,1844],{"class":118},[33,2420,2421],{"class":215}," html",[33,2423,639],{"class":118},[33,2425,2426],{"class":159}," renderComponent",[33,2428,2429],{"class":122},"({ title: ",[33,2431,2432],{"class":129},"\"Hello\"",[33,2434,1262],{"class":122},[33,2436,2437,2439,2442,2445,2447],{"class":35,"line":371},[33,2438,1939],{"class":159},[33,2440,2441],{"class":122},"(html).",[33,2443,2444],{"class":159},"toMatchInlineSnapshot",[33,2446,163],{"class":122},[33,2448,2449],{"class":129},"`\n",[33,2451,2452,2455,2458,2461,2463],{"class":35,"line":388},[33,2453,2454],{"class":129},"      \"\u003Cdiv class=",[33,2456,2457],{"class":215},"\\\\",[33,2459,2460],{"class":129},"\"title",[33,2462,2457],{"class":215},[33,2464,2465],{"class":129},"\">Hello\u003C\u002Fdiv>\"\n",[33,2467,2468,2471],{"class":35,"line":404},[33,2469,2470],{"class":129},"    `\")",[33,2472,133],{"class":2473},"sMKGj",[33,2475,2476],{"class":35,"line":420},[33,2477,2478],{"class":39},"    \u002F\u002F If the output changes, the snapshot test fails (review and update if intentional).\n",[33,2480,2481],{"class":35,"line":436},[33,2482,357],{"class":122},[15,2484,2486],{"id":2485},"tips-tricks","💡 Tips & Tricks",[20,2488,2489],{"language":22},[24,2490,2492],{"className":26,"code":2491,"language":22,"meta":28,"style":28},"\u002F\u002F ── Test data builders (reduce boilerplate) ──\nconst createUser = (overrides = {}) => ({\n    id: Math.random(),\n    name: \"Test User\",\n    email: \"test@example.com\",\n    active: true,\n    ...overrides,  \u002F\u002F allow partial overrides\n});\ncreateUser({ name: \"Alice\" });  \u002F\u002F { id: ..., name: \"Alice\", email: ..., active: true }\n\n\u002F\u002F ── Test async code ──\nit(\"fetches user data\", async () => {\n    const user = await getUser(1);\n    expect(user.name).toBe(\"Alice\");\n});\n\n\u002F\u002F ── Test rejected promises ──\nit(\"rejects on invalid ID\", async () => {\n    await expect(getUser(-1)).rejects.toThrow(\"invalid ID\");\n});\n\n\u002F\u002F ── beforeEach for common setup ──\nbeforeEach(() => {\n    localStorage.clear();  \u002F\u002F reset state before each test\n    document.body.innerHTML = \"\";  \u002F\u002F clean DOM\n});\n\n\u002F\u002F ── Test with timers (vi.useFakeTimers) ──\nbeforeEach(() => vi.useFakeTimers());\nafterEach(() => vi.useRealTimers());\n\nit(\"debounces calls\", () => {\n    const fn = vi.fn();\n    const debounced = debounce(fn, 1000);\n    debounced(); debounced(); debounced();  \u002F\u002F multiple calls\n    vi.advanceTimersByTime(1000);  \u002F\u002F fast-forward 1s (no real waiting)\n    expect(fn).toHaveBeenCalledTimes(1);  \u002F\u002F only called once after debounce\n});\n",[30,2493,2494,2499,2522,2533,2543,2553,2562,2573,2577,2592,2596,2601,2620,2638,2653,2657,2661,2666,2685,2715,2719,2723,2728,2738,2751,2767,2771,2775,2780,2796,2811,2815,2830,2845,2865,2885,2901,2918],{"__ignoreMap":28},[33,2495,2496],{"class":35,"line":36},[33,2497,2498],{"class":39},"\u002F\u002F ── Test data builders (reduce boilerplate) ──\n",[33,2500,2501,2503,2506,2508,2511,2514,2516,2518,2520],{"class":35,"line":43},[33,2502,633],{"class":118},[33,2504,2505],{"class":159}," createUser",[33,2507,639],{"class":118},[33,2509,2510],{"class":122}," (",[33,2512,2513],{"class":920},"overrides",[33,2515,639],{"class":118},[33,2517,2019],{"class":122},[33,2519,172],{"class":118},[33,2521,820],{"class":122},[33,2523,2524,2527,2530],{"class":35,"line":49},[33,2525,2526],{"class":122},"    id: Math.",[33,2528,2529],{"class":159},"random",[33,2531,2532],{"class":122},"(),\n",[33,2534,2535,2538,2541],{"class":35,"line":55},[33,2536,2537],{"class":122},"    name: ",[33,2539,2540],{"class":129},"\"Test User\"",[33,2542,1406],{"class":122},[33,2544,2545,2548,2551],{"class":35,"line":61},[33,2546,2547],{"class":122},"    email: ",[33,2549,2550],{"class":129},"\"test@example.com\"",[33,2552,1406],{"class":122},[33,2554,2555,2558,2560],{"class":35,"line":67},[33,2556,2557],{"class":122},"    active: ",[33,2559,754],{"class":215},[33,2561,1406],{"class":122},[33,2563,2564,2567,2570],{"class":35,"line":73},[33,2565,2566],{"class":118},"    ...",[33,2568,2569],{"class":122},"overrides,  ",[33,2571,2572],{"class":39},"\u002F\u002F allow partial overrides\n",[33,2574,2575],{"class":35,"line":79},[33,2576,357],{"class":122},[33,2578,2579,2582,2584,2586,2589],{"class":35,"line":84},[33,2580,2581],{"class":159},"createUser",[33,2583,1256],{"class":122},[33,2585,1259],{"class":129},[33,2587,2588],{"class":122}," });  ",[33,2590,2591],{"class":39},"\u002F\u002F { id: ..., name: \"Alice\", email: ..., active: true }\n",[33,2593,2594],{"class":35,"line":90},[33,2595,106],{"emptyLinePlaceholder":105},[33,2597,2598],{"class":35,"line":96},[33,2599,2600],{"class":39},"\u002F\u002F ── Test async code ──\n",[33,2602,2603,2605,2607,2610,2612,2614,2616,2618],{"class":35,"line":102},[33,2604,2403],{"class":159},[33,2606,163],{"class":122},[33,2608,2609],{"class":129},"\"fetches user data\"",[33,2611,219],{"class":122},[33,2613,1832],{"class":118},[33,2615,1835],{"class":122},[33,2617,172],{"class":118},[33,2619,269],{"class":122},[33,2621,2622,2624,2626,2628,2630,2632,2634,2636],{"class":35,"line":109},[33,2623,1844],{"class":118},[33,2625,1351],{"class":215},[33,2627,639],{"class":118},[33,2629,1655],{"class":118},[33,2631,1635],{"class":159},[33,2633,163],{"class":122},[33,2635,680],{"class":215},[33,2637,241],{"class":122},[33,2639,2640,2642,2645,2647,2649,2651],{"class":35,"line":115},[33,2641,1939],{"class":159},[33,2643,2644],{"class":122},"(user.name).",[33,2646,233],{"class":159},[33,2648,163],{"class":122},[33,2650,1259],{"class":129},[33,2652,241],{"class":122},[33,2654,2655],{"class":35,"line":136},[33,2656,357],{"class":122},[33,2658,2659],{"class":35,"line":151},[33,2660,106],{"emptyLinePlaceholder":105},[33,2662,2663],{"class":35,"line":156},[33,2664,2665],{"class":39},"\u002F\u002F ── Test rejected promises ──\n",[33,2667,2668,2670,2672,2675,2677,2679,2681,2683],{"class":35,"line":181},[33,2669,2403],{"class":159},[33,2671,163],{"class":122},[33,2673,2674],{"class":129},"\"rejects on invalid ID\"",[33,2676,219],{"class":122},[33,2678,1832],{"class":118},[33,2680,1835],{"class":122},[33,2682,172],{"class":118},[33,2684,269],{"class":122},[33,2686,2687,2690,2693,2695,2697,2699,2701,2703,2706,2708,2710,2713],{"class":35,"line":201},[33,2688,2689],{"class":118},"    await",[33,2691,2692],{"class":159}," expect",[33,2694,163],{"class":122},[33,2696,1924],{"class":159},[33,2698,163],{"class":122},[33,2700,330],{"class":118},[33,2702,680],{"class":215},[33,2704,2705],{"class":122},")).rejects.",[33,2707,530],{"class":159},[33,2709,163],{"class":122},[33,2711,2712],{"class":129},"\"invalid ID\"",[33,2714,241],{"class":122},[33,2716,2717],{"class":35,"line":244},[33,2718,357],{"class":122},[33,2720,2721],{"class":35,"line":250},[33,2722,106],{"emptyLinePlaceholder":105},[33,2724,2725],{"class":35,"line":255},[33,2726,2727],{"class":39},"\u002F\u002F ── beforeEach for common setup ──\n",[33,2729,2730,2732,2734,2736],{"class":35,"line":272},[33,2731,1053],{"class":159},[33,2733,1056],{"class":122},[33,2735,172],{"class":118},[33,2737,269],{"class":122},[33,2739,2740,2743,2746,2748],{"class":35,"line":293},[33,2741,2742],{"class":122},"    localStorage.",[33,2744,2745],{"class":159},"clear",[33,2747,1071],{"class":122},[33,2749,2750],{"class":39},"\u002F\u002F reset state before each test\n",[33,2752,2753,2756,2758,2761,2764],{"class":35,"line":298},[33,2754,2755],{"class":122},"    document.body.innerHTML ",[33,2757,1742],{"class":118},[33,2759,2760],{"class":129}," \"\"",[33,2762,2763],{"class":122},";  ",[33,2765,2766],{"class":39},"\u002F\u002F clean DOM\n",[33,2768,2769],{"class":35,"line":303},[33,2770,357],{"class":122},[33,2772,2773],{"class":35,"line":319},[33,2774,106],{"emptyLinePlaceholder":105},[33,2776,2777],{"class":35,"line":349},[33,2778,2779],{"class":39},"\u002F\u002F ── Test with timers (vi.useFakeTimers) ──\n",[33,2781,2782,2784,2786,2788,2790,2793],{"class":35,"line":354},[33,2783,1053],{"class":159},[33,2785,1056],{"class":122},[33,2787,172],{"class":118},[33,2789,642],{"class":122},[33,2791,2792],{"class":159},"useFakeTimers",[33,2794,2795],{"class":122},"());\n",[33,2797,2798,2800,2802,2804,2806,2809],{"class":35,"line":360},[33,2799,1095],{"class":159},[33,2801,1056],{"class":122},[33,2803,172],{"class":118},[33,2805,642],{"class":122},[33,2807,2808],{"class":159},"useRealTimers",[33,2810,2795],{"class":122},[33,2812,2813],{"class":35,"line":365},[33,2814,106],{"emptyLinePlaceholder":105},[33,2816,2817,2819,2821,2824,2826,2828],{"class":35,"line":371},[33,2818,2403],{"class":159},[33,2820,163],{"class":122},[33,2822,2823],{"class":129},"\"debounces calls\"",[33,2825,169],{"class":122},[33,2827,172],{"class":118},[33,2829,269],{"class":122},[33,2831,2832,2834,2837,2839,2841,2843],{"class":35,"line":388},[33,2833,1844],{"class":118},[33,2835,2836],{"class":215}," fn",[33,2838,639],{"class":118},[33,2840,642],{"class":122},[33,2842,645],{"class":159},[33,2844,648],{"class":122},[33,2846,2847,2849,2852,2854,2857,2860,2863],{"class":35,"line":404},[33,2848,1844],{"class":118},[33,2850,2851],{"class":215}," debounced",[33,2853,639],{"class":118},[33,2855,2856],{"class":159}," debounce",[33,2858,2859],{"class":122},"(fn, ",[33,2861,2862],{"class":215},"1000",[33,2864,241],{"class":122},[33,2866,2867,2870,2873,2876,2878,2880,2882],{"class":35,"line":420},[33,2868,2869],{"class":159},"    debounced",[33,2871,2872],{"class":122},"(); ",[33,2874,2875],{"class":159},"debounced",[33,2877,2872],{"class":122},[33,2879,2875],{"class":159},[33,2881,1071],{"class":122},[33,2883,2884],{"class":39},"\u002F\u002F multiple calls\n",[33,2886,2887,2889,2892,2894,2896,2898],{"class":35,"line":436},[33,2888,1065],{"class":122},[33,2890,2891],{"class":159},"advanceTimersByTime",[33,2893,163],{"class":122},[33,2895,2862],{"class":215},[33,2897,788],{"class":122},[33,2899,2900],{"class":39},"\u002F\u002F fast-forward 1s (no real waiting)\n",[33,2902,2903,2905,2907,2909,2911,2913,2915],{"class":35,"line":452},[33,2904,1939],{"class":159},[33,2906,527],{"class":122},[33,2908,675],{"class":159},[33,2910,163],{"class":122},[33,2912,680],{"class":215},[33,2914,788],{"class":122},[33,2916,2917],{"class":39},"\u002F\u002F only called once after debounce\n",[33,2919,2920],{"class":35,"line":468},[33,2921,357],{"class":122},[15,2923,2925],{"id":2924},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[20,2927,2928],{"language":22},[24,2929,2931],{"className":26,"code":2930,"language":22,"meta":28,"style":28},"\u002F\u002F ── Don't test implementation details (test behavior, not internals) ──\n\u002F\u002F Tests that check internal state, call counts, or data structures break on refactoring.\n\u002F\u002F Test the public API and observable behavior.\n\n\u002F\u002F ── `toEqual` does deep equality; `toBe` does strict equality ──\nexpect({ a: 1 }).toEqual({ a: 1 });  \u002F\u002F ✓ (deep — same content)\nexpect({ a: 1 }).toBe({ a: 1 });    \u002F\u002F ✗ (different object references)\n\n\u002F\u002F ── `toBeNaN` (not `toBe(NaN)`) ──\nexpect(NaN).toBeNaN();  \u002F\u002F ✓ (NaN !== NaN, so toBe(NaN) would fail)\n\n\u002F\u002F ── Mock cleanup: clear mocks between tests to avoid cross-test pollution ──\nbeforeEach(() => vi.clearAllMocks());  \u002F\u002F reset call counts\n\n\u002F\u002F ── vi.mock is hoisted (runs before imports) ──\n\u002F\u002F vi.mock(\".\u002Fapi.js\", () => ({ fetchData: vi.fn() }));\n\u002F\u002F This is hoisted to the top — even imports below are mocked.\n\u002F\u002F Can't use variables defined later in the mock factory (hoisting breaks references).\n\n\u002F\u002F ── Snapshot tests can hide bugs (update too easily) ──\n\u002F\u002F If the output changes, the test fails, but `--update-snapshot` silently passes.\n\u002F\u002F Review snapshot changes carefully — don't blindly update.\n\n\u002F\u002F ── Testing async: always use async\u002Fawait or expect.assertions ──\nit(\"async test\", async () => {\n    expect.assertions(1);  \u002F\u002F ensure exactly 1 assertion runs (catches swallowed async)\n    await someAsyncFunction();\n    expect(result).toBe(true);\n});\n",[30,2932,2933,2938,2943,2948,2952,2957,2980,3002,3006,3011,3030,3034,3039,3057,3061,3066,3071,3076,3081,3085,3090,3095,3100,3104,3109,3128,3145,3154,3169],{"__ignoreMap":28},[33,2934,2935],{"class":35,"line":36},[33,2936,2937],{"class":39},"\u002F\u002F ── Don't test implementation details (test behavior, not internals) ──\n",[33,2939,2940],{"class":35,"line":43},[33,2941,2942],{"class":39},"\u002F\u002F Tests that check internal state, call counts, or data structures break on refactoring.\n",[33,2944,2945],{"class":35,"line":49},[33,2946,2947],{"class":39},"\u002F\u002F Test the public API and observable behavior.\n",[33,2949,2950],{"class":35,"line":55},[33,2951,106],{"emptyLinePlaceholder":105},[33,2953,2954],{"class":35,"line":61},[33,2955,2956],{"class":39},"\u002F\u002F ── `toEqual` does deep equality; `toBe` does strict equality ──\n",[33,2958,2959,2961,2964,2966,2969,2971,2973,2975,2977],{"class":35,"line":67},[33,2960,374],{"class":159},[33,2962,2963],{"class":122},"({ a: ",[33,2965,680],{"class":215},[33,2967,2968],{"class":122}," }).",[33,2970,395],{"class":159},[33,2972,2963],{"class":122},[33,2974,680],{"class":215},[33,2976,2588],{"class":122},[33,2978,2979],{"class":39},"\u002F\u002F ✓ (deep — same content)\n",[33,2981,2982,2984,2986,2988,2990,2992,2994,2996,2999],{"class":35,"line":73},[33,2983,374],{"class":159},[33,2985,2963],{"class":122},[33,2987,680],{"class":215},[33,2989,2968],{"class":122},[33,2991,233],{"class":159},[33,2993,2963],{"class":122},[33,2995,680],{"class":215},[33,2997,2998],{"class":122}," });    ",[33,3000,3001],{"class":39},"\u002F\u002F ✗ (different object references)\n",[33,3003,3004],{"class":35,"line":79},[33,3005,106],{"emptyLinePlaceholder":105},[33,3007,3008],{"class":35,"line":84},[33,3009,3010],{"class":39},"\u002F\u002F ── `toBeNaN` (not `toBe(NaN)`) ──\n",[33,3012,3013,3015,3017,3020,3023,3025,3027],{"class":35,"line":90},[33,3014,374],{"class":159},[33,3016,163],{"class":122},[33,3018,3019],{"class":215},"NaN",[33,3021,3022],{"class":122},").",[33,3024,475],{"class":159},[33,3026,1071],{"class":122},[33,3028,3029],{"class":39},"\u002F\u002F ✓ (NaN !== NaN, so toBe(NaN) would fail)\n",[33,3031,3032],{"class":35,"line":96},[33,3033,106],{"emptyLinePlaceholder":105},[33,3035,3036],{"class":35,"line":102},[33,3037,3038],{"class":39},"\u002F\u002F ── Mock cleanup: clear mocks between tests to avoid cross-test pollution ──\n",[33,3040,3041,3043,3045,3047,3049,3051,3054],{"class":35,"line":109},[33,3042,1053],{"class":159},[33,3044,1056],{"class":122},[33,3046,172],{"class":118},[33,3048,642],{"class":122},[33,3050,1068],{"class":159},[33,3052,3053],{"class":122},"());  ",[33,3055,3056],{"class":39},"\u002F\u002F reset call counts\n",[33,3058,3059],{"class":35,"line":115},[33,3060,106],{"emptyLinePlaceholder":105},[33,3062,3063],{"class":35,"line":136},[33,3064,3065],{"class":39},"\u002F\u002F ── vi.mock is hoisted (runs before imports) ──\n",[33,3067,3068],{"class":35,"line":151},[33,3069,3070],{"class":39},"\u002F\u002F vi.mock(\".\u002Fapi.js\", () => ({ fetchData: vi.fn() }));\n",[33,3072,3073],{"class":35,"line":156},[33,3074,3075],{"class":39},"\u002F\u002F This is hoisted to the top — even imports below are mocked.\n",[33,3077,3078],{"class":35,"line":181},[33,3079,3080],{"class":39},"\u002F\u002F Can't use variables defined later in the mock factory (hoisting breaks references).\n",[33,3082,3083],{"class":35,"line":201},[33,3084,106],{"emptyLinePlaceholder":105},[33,3086,3087],{"class":35,"line":244},[33,3088,3089],{"class":39},"\u002F\u002F ── Snapshot tests can hide bugs (update too easily) ──\n",[33,3091,3092],{"class":35,"line":250},[33,3093,3094],{"class":39},"\u002F\u002F If the output changes, the test fails, but `--update-snapshot` silently passes.\n",[33,3096,3097],{"class":35,"line":255},[33,3098,3099],{"class":39},"\u002F\u002F Review snapshot changes carefully — don't blindly update.\n",[33,3101,3102],{"class":35,"line":272},[33,3103,106],{"emptyLinePlaceholder":105},[33,3105,3106],{"class":35,"line":293},[33,3107,3108],{"class":39},"\u002F\u002F ── Testing async: always use async\u002Fawait or expect.assertions ──\n",[33,3110,3111,3113,3115,3118,3120,3122,3124,3126],{"class":35,"line":298},[33,3112,2403],{"class":159},[33,3114,163],{"class":122},[33,3116,3117],{"class":129},"\"async test\"",[33,3119,219],{"class":122},[33,3121,1832],{"class":118},[33,3123,1835],{"class":122},[33,3125,172],{"class":118},[33,3127,269],{"class":122},[33,3129,3130,3133,3136,3138,3140,3142],{"class":35,"line":303},[33,3131,3132],{"class":122},"    expect.",[33,3134,3135],{"class":159},"assertions",[33,3137,163],{"class":122},[33,3139,680],{"class":215},[33,3141,788],{"class":122},[33,3143,3144],{"class":39},"\u002F\u002F ensure exactly 1 assertion runs (catches swallowed async)\n",[33,3146,3147,3149,3152],{"class":35,"line":319},[33,3148,2689],{"class":118},[33,3150,3151],{"class":159}," someAsyncFunction",[33,3153,648],{"class":122},[33,3155,3156,3158,3161,3163,3165,3167],{"class":35,"line":349},[33,3157,1939],{"class":159},[33,3159,3160],{"class":122},"(result).",[33,3162,233],{"class":159},[33,3164,163],{"class":122},[33,3166,754],{"class":215},[33,3168,241],{"class":122},[33,3170,3171],{"class":35,"line":354},[33,3172,357],{"class":122},[15,3174,3176],{"id":3175},"quick-quiz","🧠 Quick Quiz",[3178,3179,3180],"p",{},"Why is this test fragile?",[20,3182,3183],{"language":22},[24,3184,3186],{"className":26,"code":3185,"language":22,"meta":28,"style":28},"it(\"sorts users by name\", () => {\n    const users = [createUser({ name: \"Zara\" }), createUser({ name: \"Alice\" })];\n    const sorted = sortBy(users, \"name\");\n    expect(sorted[0].id).toBe(users[1].id);  \u002F\u002F fragile\n});\n",[30,3187,3188,3203,3234,3254,3279],{"__ignoreMap":28},[33,3189,3190,3192,3194,3197,3199,3201],{"class":35,"line":36},[33,3191,2403],{"class":159},[33,3193,163],{"class":122},[33,3195,3196],{"class":129},"\"sorts users by name\"",[33,3198,169],{"class":122},[33,3200,172],{"class":118},[33,3202,269],{"class":122},[33,3204,3205,3207,3210,3212,3215,3217,3219,3222,3225,3227,3229,3231],{"class":35,"line":43},[33,3206,1844],{"class":118},[33,3208,3209],{"class":215}," users",[33,3211,639],{"class":118},[33,3213,3214],{"class":122}," [",[33,3216,2581],{"class":159},[33,3218,1256],{"class":122},[33,3220,3221],{"class":129},"\"Zara\"",[33,3223,3224],{"class":122}," }), ",[33,3226,2581],{"class":159},[33,3228,1256],{"class":122},[33,3230,1259],{"class":129},[33,3232,3233],{"class":122}," })];\n",[33,3235,3236,3238,3241,3243,3246,3249,3252],{"class":35,"line":49},[33,3237,1844],{"class":118},[33,3239,3240],{"class":215}," sorted",[33,3242,639],{"class":118},[33,3244,3245],{"class":159}," sortBy",[33,3247,3248],{"class":122},"(users, ",[33,3250,3251],{"class":129},"\"name\"",[33,3253,241],{"class":122},[33,3255,3256,3258,3261,3263,3266,3268,3271,3273,3276],{"class":35,"line":55},[33,3257,1939],{"class":159},[33,3259,3260],{"class":122},"(sorted[",[33,3262,288],{"class":215},[33,3264,3265],{"class":122},"].id).",[33,3267,233],{"class":159},[33,3269,3270],{"class":122},"(users[",[33,3272,680],{"class":215},[33,3274,3275],{"class":122},"].id);  ",[33,3277,3278],{"class":39},"\u002F\u002F fragile\n",[33,3280,3281],{"class":35,"line":61},[33,3282,357],{"class":122},[3284,3285,3286,3290,3305,3311,3413],"details",{},[3287,3288,3289],"summary",{},"Answer",[3178,3291,3292,3293,3297,3298,3301,3302,3304],{},"The test depends on the ",[3294,3295,3296],"strong",{},"implementation detail"," of which user object was created second (",[30,3299,3300],{},"users[1].id","). If the test data creation order changes, or if ",[30,3303,2581],{}," is refactored to generate IDs differently, the test breaks — even if the sort is correct.",[3178,3306,3307,3310],{},[3294,3308,3309],{},"Fix",": test the observable behavior (the sorted order of names), not the implementation detail (which object ID is first):",[24,3312,3314],{"className":26,"code":3313,"language":22,"meta":28,"style":28},"it(\"sorts users by name\", () => {\n    const users = [createUser({ name: \"Zara\" }), createUser({ name: \"Alice\" })];\n    const sorted = sortBy(users, \"name\");\n    expect(sorted.map(u => u.name)).toEqual([\"Alice\", \"Zara\"]);  \u002F\u002F test behavior\n});\n",[30,3315,3316,3330,3356,3372,3409],{"__ignoreMap":28},[33,3317,3318,3320,3322,3324,3326,3328],{"class":35,"line":36},[33,3319,2403],{"class":159},[33,3321,163],{"class":122},[33,3323,3196],{"class":129},[33,3325,169],{"class":122},[33,3327,172],{"class":118},[33,3329,269],{"class":122},[33,3331,3332,3334,3336,3338,3340,3342,3344,3346,3348,3350,3352,3354],{"class":35,"line":43},[33,3333,1844],{"class":118},[33,3335,3209],{"class":215},[33,3337,639],{"class":118},[33,3339,3214],{"class":122},[33,3341,2581],{"class":159},[33,3343,1256],{"class":122},[33,3345,3221],{"class":129},[33,3347,3224],{"class":122},[33,3349,2581],{"class":159},[33,3351,1256],{"class":122},[33,3353,1259],{"class":129},[33,3355,3233],{"class":122},[33,3357,3358,3360,3362,3364,3366,3368,3370],{"class":35,"line":49},[33,3359,1844],{"class":118},[33,3361,3240],{"class":215},[33,3363,639],{"class":118},[33,3365,3245],{"class":159},[33,3367,3248],{"class":122},[33,3369,3251],{"class":129},[33,3371,241],{"class":122},[33,3373,3374,3376,3379,3382,3384,3387,3390,3393,3395,3397,3399,3401,3403,3406],{"class":35,"line":55},[33,3375,1939],{"class":159},[33,3377,3378],{"class":122},"(sorted.",[33,3380,3381],{"class":159},"map",[33,3383,163],{"class":122},[33,3385,3386],{"class":920},"u",[33,3388,3389],{"class":118}," =>",[33,3391,3392],{"class":122}," u.name)).",[33,3394,395],{"class":159},[33,3396,212],{"class":122},[33,3398,1259],{"class":129},[33,3400,219],{"class":122},[33,3402,3221],{"class":129},[33,3404,3405],{"class":122},"]);  ",[33,3407,3408],{"class":39},"\u002F\u002F test behavior\n",[33,3410,3411],{"class":35,"line":61},[33,3412,357],{"class":122},[3178,3414,3415,3418],{},[3294,3416,3417],{},"The lesson",": test what the user observes (the sorted names), not implementation details (object IDs, internal order). Behavior tests survive refactoring; implementation tests break when internals change.",[3420,3421,3422],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .svAP2, html code.shiki .svAP2{--shiki-default:#032F62;--shiki-github-dark:#DBEDFF}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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html pre.shiki code .sMKGj, html code.shiki .sMKGj{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-github-dark:#FDAEB7;--shiki-github-dark-font-style:italic}",{"title":28,"searchDepth":43,"depth":43,"links":3424},[3425,3426,3428,3429,3430,3431,3432,3433],{"id":17,"depth":43,"text":18},{"id":588,"depth":43,"text":3427},"Mocking: vi.fn, vi.mock, and vi.spyOn",{"id":1120,"depth":43,"text":1121},{"id":1596,"depth":43,"text":1597},{"id":2112,"depth":43,"text":2113},{"id":2485,"depth":43,"text":2486},{"id":2924,"depth":43,"text":2925},{"id":3175,"depth":43,"text":3176},"Deep-dive into JavaScript testing: test types (unit\u002Fintegration\u002Fe2e), Vitest setup and assertions, mocking with vi.mock and vi.fn, dependency injection for testability, snapshot testing, and the TDD red-green-refactor cycle. Code-first reference for senior engineers.","md",{},"\u002Fjs\u002F25-testing",{"title":5,"description":3434},"js\u002F25-testing","QDC84OQ6BnLO4g8ifj2eRg6YzEArLZHRcj2VS0JKR_c",1789924649537]