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