{"id":360,"date":"2026-01-26T01:24:47","date_gmt":"2026-01-25T17:24:47","guid":{"rendered":"https:\/\/skwp.zeabur.app\/2026\/01\/26\/%e3%80%90vitest-react-%e6%b8%ac%e8%a9%a6%e6%95%99%e5%ad%b8%e3%80%9103-mock-%e8%88%87%e9%9d%9e%e5%90%8c%e6%ad%a5%e6%b8%ac%e8%a9%a6%ef%bc%9avi-fn%e3%80%81vi-mock-%e8%88%87-waitfor\/"},"modified":"2026-01-26T01:24:47","modified_gmt":"2026-01-25T17:24:47","slug":"%e3%80%90vitest-react-%e6%b8%ac%e8%a9%a6%e6%95%99%e5%ad%b8%e3%80%9103-mock-%e8%88%87%e9%9d%9e%e5%90%8c%e6%ad%a5%e6%b8%ac%e8%a9%a6%ef%bc%9avi-fn%e3%80%81vi-mock-%e8%88%87-waitfor","status":"publish","type":"post","link":"https:\/\/blog.itsmygo.uk\/wordpress\/2026\/01\/26\/%e3%80%90vitest-react-%e6%b8%ac%e8%a9%a6%e6%95%99%e5%ad%b8%e3%80%9103-mock-%e8%88%87%e9%9d%9e%e5%90%8c%e6%ad%a5%e6%b8%ac%e8%a9%a6%ef%bc%9avi-fn%e3%80%81vi-mock-%e8%88%87-waitfor\/","title":{"rendered":"\u3010Vitest React \u6e2c\u8a66\u6559\u5b78\u3011#03 Mock \u8207\u975e\u540c\u6b65\u6e2c\u8a66\uff1avi.fn\u3001vi.mock \u8207 waitFor"},"content":{"rendered":"\n<div id=\"quiz-basic\">\n<style>\n#quiz-basic .question {\n            background: white;\n            border-radius: 8px;\n            padding: 20px;\n            margin: 20px 0;\n            box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n        }#quiz-basic .question h3 { margin-top: 0; color: #333; }#quiz-basic .options { list-style: none; padding: 0; }#quiz-basic .options li {\n            padding: 12px 15px;\n            margin: 8px 0;\n            border: 2px solid #ddd;\n            border-radius: 6px;\n            cursor: pointer;\n            transition: all 0.2s;\n        }#quiz-basic .options li:hover:not(.disabled) {\n            border-color: #4CAF50;\n            background: #f0fff0;\n        }#quiz-basic .options li.correct {\n            border-color: #4CAF50;\n            background: #e8f5e9;\n        }#quiz-basic .options li.wrong {\n            border-color: #f44336;\n            background: #ffebee;\n        }#quiz-basic .options li.disabled { cursor: default; opacity: 0.7; }#quiz-basic .options li.show-answer { border-color: #4CAF50; background: #e8f5e9; }#quiz-basic .feedback {\n            margin-top: 15px;\n            padding: 12px;\n            border-radius: 6px;\n            display: none;\n        }#quiz-basic .feedback.correct { background: #e8f5e9; color: #2e7d32; display: block; }#quiz-basic .feedback.wrong { background: #ffebee; color: #c62828; display: block; }#quiz-basic .score {\n            text-align: center;\n            font-size: 1.5em;\n            padding: 20px;\n            background: white;\n            border-radius: 8px;\n            margin-top: 20px;\n        }\n<\/style>\n<h1>\u6e2c\u9a57\uff1aMock \u8207\u975e\u540c\u6b65\u6e2c\u8a66<\/h1>\n    <p>\u5171 5 \u984c\uff0c\u9ede\u9078\u7b54\u6848\u5f8c\u6703\u7acb\u5373\u986f\u793a\u7d50\u679c<\/p>\n\n    <div class=\"question\" id=\"q1\">\n        <h3>1. <code>vi.fn()<\/code> \u5efa\u7acb\u7684 mock \u51fd\u6578\u53ef\u4ee5\u505a\u4ec0\u9ebc\uff1f<\/h3>\n        <ul class=\"options\">\n            <li onclick=\"checkAnswer(1, 'A', 'C')\">A. \u53ea\u80fd\u7528\u4f86\u6a21\u64ec API \u547c\u53eb<\/li>\n            <li onclick=\"checkAnswer(1, 'B', 'C')\">B. \u53ea\u80fd\u8ffd\u8e64\u51fd\u6578\u88ab\u547c\u53eb\u5e7e\u6b21<\/li>\n            <li onclick=\"checkAnswer(1, 'C', 'C')\">C. \u8ffd\u8e64\u547c\u53eb\u6b21\u6578\u3001\u53c3\u6578\uff0c\u4e26\u53ef\u8a2d\u5b9a\u56de\u50b3\u503c<\/li>\n            <li onclick=\"checkAnswer(1, 'D', 'C')\">D. \u76f4\u63a5\u57f7\u884c\u539f\u672c\u51fd\u6578\u7684\u908f\u8f2f<\/li>\n        <\/ul>\n        <div class=\"feedback\" id=\"fb1\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q2\">\n        <h3>2. \u7576\u4f60\u60f3\u6a21\u64ec\u4e00\u500b Promise \u6210\u529f\u56de\u50b3\u8cc7\u6599\u6642\uff0c\u61c9\u8a72\u4f7f\u7528\u54ea\u500b\u65b9\u6cd5\uff1f<\/h3>\n        <ul class=\"options\">\n            <li onclick=\"checkAnswer(2, 'A', 'B')\">A. <code>mockReturnValue()<\/code><\/li>\n            <li onclick=\"checkAnswer(2, 'B', 'B')\">B. <code>mockResolvedValue()<\/code><\/li>\n            <li onclick=\"checkAnswer(2, 'C', 'B')\">C. <code>mockRejectedValue()<\/code><\/li>\n            <li onclick=\"checkAnswer(2, 'D', 'B')\">D. <code>mockAsyncValue()<\/code><\/li>\n        <\/ul>\n        <div class=\"feedback\" id=\"fb2\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q3\">\n        <h3>3. <code>vi.mock('.\/api')<\/code> \u9019\u884c\u7a0b\u5f0f\u78bc\u505a\u4e86\u4ec0\u9ebc\uff1f<\/h3>\n        <ul class=\"options\">\n            <li onclick=\"checkAnswer(3, 'A', 'D')\">A. \u57f7\u884c api \u6a21\u7d44\u4e2d\u7684\u6240\u6709\u51fd\u6578<\/li>\n            <li onclick=\"checkAnswer(3, 'B', 'D')\">B. \u522a\u9664 api \u6a21\u7d44\u7684\u6240\u6709\u532f\u51fa<\/li>\n            <li onclick=\"checkAnswer(3, 'C', 'D')\">C. \u53ea\u6a21\u64ec api \u6a21\u7d44\u7684\u9810\u8a2d\u532f\u51fa<\/li>\n            <li onclick=\"checkAnswer(3, 'D', 'D')\">D. \u5c07 api \u6a21\u7d44\u7684\u6240\u6709\u532f\u51fa\u51fd\u6578\u66ff\u63db\u70ba mock \u51fd\u6578<\/li>\n        <\/ul>\n        <div class=\"feedback\" id=\"fb3\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q4\">\n        <h3>4. <code>getByRole<\/code> \u548c <code>findByRole<\/code> \u7684\u4e3b\u8981\u5dee\u7570\u662f\u4ec0\u9ebc\uff1f<\/h3>\n        <ul class=\"options\">\n            <li onclick=\"checkAnswer(4, 'A', 'C')\">A. <code>getByRole<\/code> \u6703\u7b49\u5f85\u5143\u7d20\u51fa\u73fe\uff0c<code>findByRole<\/code> \u4e0d\u6703<\/li>\n            <li onclick=\"checkAnswer(4, 'B', 'C')\">B. \u5169\u8005\u5b8c\u5168\u76f8\u540c\uff0c\u53ea\u662f\u540d\u7a31\u4e0d\u540c<\/li>\n            <li onclick=\"checkAnswer(4, 'C', 'C')\">C. <code>getByRole<\/code> \u662f\u540c\u6b65\u67e5\u8a62\uff0c<code>findByRole<\/code> \u6703\u7b49\u5f85\u5143\u7d20\u51fa\u73fe<\/li>\n            <li onclick=\"checkAnswer(4, 'D', 'C')\">D. <code>findByRole<\/code> \u53ea\u80fd\u67e5\u8a62\u6309\u9215\u5143\u7d20<\/li>\n        <\/ul>\n        <div class=\"feedback\" id=\"fb4\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q5\">\n        <h3>5. <code>waitFor<\/code> \u51fd\u6578\u7684\u4f5c\u7528\u662f\u4ec0\u9ebc\uff1f<\/h3>\n        <ul class=\"options\">\n            <li onclick=\"checkAnswer(5, 'A', 'B')\">A. \u8b93\u6e2c\u8a66\u66ab\u505c\u56fa\u5b9a\u79d2\u6578<\/li>\n            <li onclick=\"checkAnswer(5, 'B', 'B')\">B. \u91cd\u8907\u57f7\u884c\u50b3\u5165\u7684\u51fd\u6578\uff0c\u76f4\u5230\u6210\u529f\u6216\u8d85\u6642<\/li>\n            <li onclick=\"checkAnswer(5, 'C', 'B')\">C. \u7b49\u5f85\u6240\u6709 API \u547c\u53eb\u5b8c\u6210<\/li>\n            <li onclick=\"checkAnswer(5, 'D', 'B')\">D. \u53d6\u6d88\u6b63\u5728\u9032\u884c\u7684\u975e\u540c\u6b65\u64cd\u4f5c<\/li>\n        <\/ul>\n        <div class=\"feedback\" id=\"fb5\"><\/div>\n    <\/div>\n\n    <div class=\"score\" id=\"score\" style=\"display:none;\"><\/div>\n<script>\n\n        let answered = 0;\n        let correct = 0;\n        const total = 5;\n        const explanations = {\n            1: \"<code>vi.fn()<\/code> \u5efa\u7acb\u7684 mock \u51fd\u6578\u53ef\u4ee5\u8a18\u9304\u88ab\u547c\u53eb\u5e7e\u6b21\u3001\u50b3\u5165\u4ec0\u9ebc\u53c3\u6578\uff0c\u4e26\u4e14\u53ef\u4ee5\u8a2d\u5b9a\u56de\u50b3\u503c\u3002\u5b83\u672c\u8eab\u4e0d\u57f7\u884c\u4efb\u4f55\u908f\u8f2f\uff0c\u53ea\u662f\u4e00\u500b\u300c\u5047\u51fd\u6578\u300d\u7528\u4f86\u8ffd\u8e64\u548c\u6a21\u64ec\u3002\",\n            2: \"<code>mockResolvedValue()<\/code> \u7528\u65bc\u6a21\u64ec Promise \u6210\u529f\u56de\u50b3\u3002<code>mockReturnValue()<\/code> \u7528\u65bc\u540c\u6b65\u56de\u50b3\u503c\uff0c<code>mockRejectedValue()<\/code> \u7528\u65bc\u6a21\u64ec Promise \u5931\u6557\u3002\",\n            3: \"<code>vi.mock('.\/api')<\/code> \u6703\u6514\u622a\u8a72\u6a21\u7d44\u7684\u532f\u5165\uff0c\u81ea\u52d5\u5c07\u6240\u6709\u532f\u51fa\u7684\u51fd\u6578\u66ff\u63db\u70ba mock \u51fd\u6578\uff0c\u9019\u4e9b mock \u51fd\u6578\u9810\u8a2d\u56de\u50b3 <code>undefined<\/code>\u3002\",\n            4: \"<code>getByRole<\/code> \u662f\u540c\u6b65\u67e5\u8a62\uff0c\u5143\u7d20\u4e0d\u5b58\u5728\u6703\u7acb\u5373\u5931\u6557\u3002<code>findByRole<\/code> \u662f\u975e\u540c\u6b65\u67e5\u8a62\uff0c\u6703\u7b49\u5f85\u5143\u7d20\u51fa\u73fe\uff08\u9810\u8a2d\u6700\u591a 1 \u79d2\uff09\u3002\",\n            5: \"<code>waitFor<\/code> \u6703\u91cd\u8907\u57f7\u884c\u50b3\u5165\u7684\u51fd\u6578\uff0c\u5982\u679c\u51fd\u6578\u62cb\u51fa\u932f\u8aa4\u5c31\u7b49\u5f85\u4e00\u5c0f\u6bb5\u6642\u9593\u5f8c\u91cd\u8a66\uff0c\u76f4\u5230\u6210\u529f\u6216\u8d85\u6642\uff08\u9810\u8a2d 1 \u79d2\uff09\u3002\u5b83\u4e0d\u662f\u56fa\u5b9a\u7b49\u5f85\u6642\u9593\uff0c\u800c\u662f\u667a\u80fd\u7b49\u5f85\u689d\u4ef6\u6210\u7acb\u3002\"\n        };\n\n        function checkAnswer(qNum, selected, correctAns) {\n            const question = document.getElementById('q' + qNum);\n            if (question.classList.contains('answered')) return;\n\n            question.classList.add('answered');\n            answered++;\n\n            const options = question.querySelectorAll('.options li');\n            options.forEach(opt => {\n                opt.classList.add('disabled');\n                if (opt.textContent.startsWith(correctAns + '.')) {\n                    opt.classList.add('show-answer');\n                }\n            });\n\n            const feedback = document.getElementById('fb' + qNum);\n            if (selected === correctAns) {\n                correct++;\n                feedback.className = 'feedback correct';\n                feedback.innerHTML = '\u2713 \u6b63\u78ba\uff01' + explanations[qNum];\n            } else {\n                feedback.className = 'feedback wrong';\n                feedback.innerHTML = '\u2717 \u932f\u8aa4\u3002\u6b63\u78ba\u7b54\u6848\u662f ' + correctAns + '\u3002' + explanations[qNum];\n                options.forEach(opt => {\n                    if (opt.textContent.startsWith(selected + '.')) {\n                        opt.classList.add('wrong');\n                    }\n                });\n            }\n\n            if (answered === total) {\n                const scoreDiv = document.getElementById('score');\n                scoreDiv.style.display = 'block';\n                scoreDiv.innerHTML = '\u6e2c\u9a57\u5b8c\u6210\uff01\u5f97\u5206\uff1a' + correct + ' \/ ' + total +\n                    (correct === total ? ' \ud83c\udf89 \u5168\u5c0d\uff01' : correct >= 3 ? ' \ud83d\udc4d \u4e0d\u932f\uff01' : ' \ud83d\udcda \u518d\u8907\u7fd2\u4e00\u4e0b\u5427');\n            }\n        }\n    \n<\/script>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">\u524d\u8a00<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u5728\u524d\u4e00\u7bc7\u6587\u7ae0\u4e2d\uff0c\u6211\u5011\u5b78\u6703\u4e86\u5982\u4f55\u6e2c\u8a66 React \u5143\u4ef6\u7684\u6e32\u67d3\u548c\u4f7f\u7528\u8005\u4e92\u52d5\u3002\u4f46\u771f\u5be6\u4e16\u754c\u7684\u5143\u4ef6\u5f80\u5f80\u9700\u8981\u547c\u53eb API\u3001\u8655\u7406\u975e\u540c\u6b65\u64cd\u4f5c\uff0c\u6216\u4f9d\u8cf4\u5916\u90e8\u6a21\u7d44\u3002\u9019\u6642\u5019\uff0c\u6211\u5011\u9700\u8981\u5b78\u6703\u5169\u500b\u95dc\u9375\u6280\u80fd\uff1a<strong>Mock\uff08\u6a21\u64ec\uff09<\/strong> \u548c<strong>\u975e\u540c\u6b65\u6e2c\u8a66<\/strong>\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u672c\u7bc7\u5c07\u5e36\u4f60\u7406\u89e3\u9019\u4e9b\u6e2c\u8a66\u7a0b\u5f0f\u78bc\u5728\u505a\u4ec0\u9ebc\uff0c\u8b93\u4f60\u5728\u95b1\u8b80\u5c08\u6848\u4e2d\u7684\u6e2c\u8a66\u6642\u4e0d\u518d\u56f0\u60d1\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">vi.fn()\uff1a\u5efa\u7acb Mock \u51fd\u6578<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u6700\u5c0f\u7bc4\u4f8b<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { vi, expect, test } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'vitest'<\/span>\n\ntest(<span class=\"hljs-string\">'mock function tracks calls'<\/span>, () =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> mockFn = vi.fn()\n\n  mockFn(<span class=\"hljs-string\">'hello'<\/span>)\n  mockFn(<span class=\"hljs-string\">'world'<\/span>)\n\n  expect(mockFn).toHaveBeenCalledTimes(<span class=\"hljs-number\">2<\/span>)\n  expect(mockFn).toHaveBeenCalledWith(<span class=\"hljs-string\">'hello'<\/span>)\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u89e3\u8b80\u91cd\u9ede<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u7576\u4f60\u770b\u5230 <code>vi.fn()<\/code> \u6642\uff0c\u5b83\u5728\u505a\u9019\u4ef6\u4e8b\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u5efa\u7acb\u4e00\u500b\u300c\u5047\u51fd\u6578\u300d<\/strong>\uff1a\u9019\u500b\u51fd\u6578\u53ef\u4ee5\u88ab\u547c\u53eb\uff0c\u4f46\u4e0d\u6703\u57f7\u884c\u4efb\u4f55\u908f\u8f2f<\/li>\n<li><strong>\u8a18\u9304\u6240\u6709\u547c\u53eb<\/strong>\uff1a\u5b83\u6703\u8a18\u4f4f\u88ab\u547c\u53eb\u5e7e\u6b21\u3001\u50b3\u5165\u4ec0\u9ebc\u53c3\u6578<\/li>\n<li><strong>\u53ef\u4ee5\u8a2d\u5b9a\u56de\u50b3\u503c<\/strong>\uff1a\u8b93\u5b83\u56de\u50b3\u4f60\u6307\u5b9a\u7684\u503c<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">\u5e38\u898b\u7684 Mock \u51fd\u6578\u7528\u6cd5<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ \u8a2d\u5b9a\u56de\u50b3\u503c<\/span>\n<span class=\"hljs-keyword\">const<\/span> mockFn = vi.fn().mockReturnValue(<span class=\"hljs-number\">42<\/span>)\nexpect(mockFn()).toBe(<span class=\"hljs-number\">42<\/span>)\n\n<span class=\"hljs-comment\">\/\/ \u6a21\u64ec Promise \u6210\u529f<\/span>\n<span class=\"hljs-keyword\">const<\/span> mockAsync = vi.fn().mockResolvedValue({ <span class=\"hljs-attr\">data<\/span>: <span class=\"hljs-string\">'success'<\/span> })\n<span class=\"hljs-keyword\">await<\/span> expect(mockAsync()).resolves.toEqual({ <span class=\"hljs-attr\">data<\/span>: <span class=\"hljs-string\">'success'<\/span> })\n\n<span class=\"hljs-comment\">\/\/ \u6a21\u64ec Promise \u5931\u6557<\/span>\n<span class=\"hljs-keyword\">const<\/span> mockError = vi.fn().mockRejectedValue(<span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">'Failed'<\/span>))\n<span class=\"hljs-keyword\">await<\/span> expect(mockError()).rejects.toThrow(<span class=\"hljs-string\">'Failed'<\/span>)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u5be6\u969b\u61c9\u7528\uff1a\u6e2c\u8a66\u6309\u9215\u9ede\u64ca<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { render, screen } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@testing-library\/react'<\/span>\n<span class=\"hljs-keyword\">import<\/span> userEvent <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@testing-library\/user-event'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { vi, expect, test } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'vitest'<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Button<\/span>(<span class=\"hljs-params\">{ onClick, children }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{onClick}<\/span>&gt;<\/span>{children}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span><\/span>\n}\n\ntest(<span class=\"hljs-string\">'calls onClick when clicked'<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> handleClick = vi.fn()\n  <span class=\"hljs-keyword\">const<\/span> user = userEvent.setup()\n\n  render(<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{handleClick}<\/span>&gt;<\/span>Click me<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Button<\/span>&gt;<\/span><\/span>)\n\n  <span class=\"hljs-keyword\">await<\/span> user.click(screen.getByRole(<span class=\"hljs-string\">'button'<\/span>))\n\n  expect(handleClick).toHaveBeenCalledTimes(<span class=\"hljs-number\">1<\/span>)\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u89e3\u8b80<\/strong>\uff1a\u9019\u500b\u6e2c\u8a66\u9a57\u8b49\u300c\u7576\u6309\u9215\u88ab\u9ede\u64ca\u6642\uff0conClick \u51fd\u6578\u6703\u88ab\u547c\u53eb\u300d\u3002\u6211\u5011\u7528 <code>vi.fn()<\/code> \u5efa\u7acb\u4e00\u500b mock \u51fd\u6578\u4f86\u8ffd\u8e64\u5b83\u662f\u5426\u88ab\u547c\u53eb\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">vi.mock()\uff1a\u6a21\u64ec\u6574\u500b\u6a21\u7d44<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u70ba\u4ec0\u9ebc\u9700\u8981\u6a21\u64ec\u6a21\u7d44\uff1f<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u7576\u5143\u4ef6\u4f9d\u8cf4\u5916\u90e8\u6a21\u7d44\uff08\u4f8b\u5982 API \u5ba2\u6236\u7aef\uff09\uff0c\u6211\u5011\u4e0d\u5e0c\u671b\u6e2c\u8a66\u771f\u7684\u767c\u9001\u7db2\u8def\u8acb\u6c42\u3002\u900f\u904e <code>vi.mock()<\/code>\uff0c\u6211\u5011\u53ef\u4ee5\u300c\u66ff\u63db\u300d\u6574\u500b\u6a21\u7d44\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u6700\u5c0f\u7bc4\u4f8b<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u5047\u8a2d\u6709\u4e00\u500b API \u6a21\u7d44\uff1a<\/p>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ api.ts<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">fetchUser<\/span>(<span class=\"hljs-params\">id: string<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">`\/api\/users\/<span class=\"hljs-subst\">${id}<\/span>`<\/span>)\n  <span class=\"hljs-keyword\">return<\/span> response.json()\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">\u6e2c\u8a66\u6642\u6211\u5011\u53ef\u4ee5\u6a21\u64ec\u5b83\uff1a<\/p>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { vi, expect, test } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'vitest'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { fetchUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/api'<\/span>\n\n<span class=\"hljs-comment\">\/\/ \u544a\u8a34 Vitest\uff1a\u7528\u5047\u7684\u6a21\u7d44\u53d6\u4ee3\u771f\u7684<\/span>\nvi.mock(<span class=\"hljs-string\">'.\/api'<\/span>)\n\ntest(<span class=\"hljs-string\">'mock module example'<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  <span class=\"hljs-comment\">\/\/ \u8a2d\u5b9a mock \u51fd\u6578\u7684\u56de\u50b3\u503c<\/span>\n  vi.mocked(fetchUser).mockResolvedValue({ <span class=\"hljs-attr\">id<\/span>: <span class=\"hljs-string\">'1'<\/span>, <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Alice'<\/span> })\n\n  <span class=\"hljs-keyword\">const<\/span> user = <span class=\"hljs-keyword\">await<\/span> fetchUser(<span class=\"hljs-string\">'1'<\/span>)\n\n  expect(user).toEqual({ <span class=\"hljs-attr\">id<\/span>: <span class=\"hljs-string\">'1'<\/span>, <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Alice'<\/span> })\n  expect(fetchUser).toHaveBeenCalledWith(<span class=\"hljs-string\">'1'<\/span>)\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u89e3\u8b80 vi.mock() \u7684\u904b\u4f5c\u65b9\u5f0f<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">vi.mock(<span class=\"hljs-string\">'.\/api'<\/span>)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">\u9019\u884c\u7a0b\u5f0f\u78bc\u544a\u8a34 Vitest\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u6514\u622a<\/strong> <code>.\/api<\/code> \u6a21\u7d44\u7684\u532f\u5165<\/li>\n<li><strong>\u81ea\u52d5\u5c07\u6240\u6709\u532f\u51fa\u7684\u51fd\u6578\u66ff\u63db\u70ba mock \u51fd\u6578<\/strong><\/li>\n<li>\u9019\u4e9b mock \u51fd\u6578\u9810\u8a2d\u56de\u50b3 <code>undefined<\/code><\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">vi.mocked() \u7684\u4f5c\u7528<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">vi<\/span><span class=\"hljs-selector-class\">.mocked<\/span>(<span class=\"hljs-selector-tag\">fetchUser<\/span>)<span class=\"hljs-selector-class\">.mockResolvedValue<\/span>(...)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><code>vi.mocked()<\/code> \u662f TypeScript \u7684\u8f14\u52a9\u51fd\u6578\uff0c\u5b83\u544a\u8a34\u7de8\u8b6f\u5668\u300c\u9019\u500b\u51fd\u6578\u5df2\u7d93\u88ab mock \u4e86\u300d\uff0c\u9019\u6a23\u4f60\u5c31\u53ef\u4ee5\u4f7f\u7528 <code>.mockResolvedValue()<\/code> \u7b49\u65b9\u6cd5\u800c\u4e0d\u6703\u6709\u578b\u5225\u932f\u8aa4\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u975e\u540c\u6b65\u6e2c\u8a66\uff1awaitFor \u8207 findBy*<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u70ba\u4ec0\u9ebc\u9700\u8981\u7b49\u5f85\uff1f<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">React \u5143\u4ef6\u7684\u72c0\u614b\u66f4\u65b0\u662f\u975e\u540c\u6b65\u7684\u3002\u7576\u4f60\u547c\u53eb API \u5f8c\u66f4\u65b0\u72c0\u614b\uff0c\u756b\u9762\u4e0d\u6703\u7acb\u5373\u6539\u8b8a\u3002\u6e2c\u8a66\u9700\u8981\u300c\u7b49\u5f85\u300d\u9019\u4e9b\u8b8a\u5316\u767c\u751f\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">findBy* vs getBy*<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ getByRole\uff1a\u540c\u6b65\u67e5\u8a62\uff0c\u5143\u7d20\u4e0d\u5b58\u5728\u6703\u7acb\u5373\u5931\u6557<\/span>\n<span class=\"hljs-keyword\">const<\/span> button = screen.getByRole(<span class=\"hljs-string\">'button'<\/span>)\n\n<span class=\"hljs-comment\">\/\/ findByRole\uff1a\u975e\u540c\u6b65\u67e5\u8a62\uff0c\u6703\u7b49\u5f85\u5143\u7d20\u51fa\u73fe\uff08\u9810\u8a2d 1 \u79d2\uff09<\/span>\n<span class=\"hljs-keyword\">const<\/span> button = <span class=\"hljs-keyword\">await<\/span> screen.findByRole(<span class=\"hljs-string\">'button'<\/span>)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u95dc\u9375\u5dee\u7570<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>getBy*<\/code>\uff1a\u300c\u73fe\u5728\u300d\u5c31\u8981\u627e\u5230\uff0c\u627e\u4e0d\u5230\u5c31\u5831\u932f<\/li>\n<li><code>findBy*<\/code>\uff1a\u300c\u7b49\u4e00\u4e0b\u300d\u518d\u627e\uff0c\u6700\u591a\u7b49 1 \u79d2<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">waitFor\uff1a\u7b49\u5f85\u689d\u4ef6\u6210\u7acb<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { render, screen, waitFor } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@testing-library\/react'<\/span>\n\ntest(<span class=\"hljs-string\">'waits for loading to complete'<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  render(<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AsyncComponent<\/span> \/&gt;<\/span><\/span>)\n\n  <span class=\"hljs-comment\">\/\/ \u7b49\u5f85 \"Loading...\" \u6d88\u5931<\/span>\n  <span class=\"hljs-keyword\">await<\/span> waitFor(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    expect(screen.queryByText(<span class=\"hljs-string\">'Loading...'<\/span>)).not.toBeInTheDocument()\n  })\n\n  <span class=\"hljs-comment\">\/\/ \u73fe\u5728\u53ef\u4ee5\u5b89\u5168\u5730\u67e5\u8a62\u8f09\u5165\u5b8c\u6210\u5f8c\u7684\u5167\u5bb9<\/span>\n  expect(screen.getByText(<span class=\"hljs-string\">'Data loaded'<\/span>)).toBeInTheDocument()\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">waitFor \u7684\u904b\u4f5c\u539f\u7406<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">await<\/span> waitFor(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  expect(something).toBe(<span class=\"hljs-literal\">true<\/span>)\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><code>waitFor<\/code> \u6703\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u57f7\u884c\u4f60\u50b3\u5165\u7684\u51fd\u6578<\/li>\n<li>\u5982\u679c\u51fd\u6578\u62cb\u51fa\u932f\u8aa4\uff08assertion \u5931\u6557\uff09\uff0c\u7b49\u5f85\u4e00\u5c0f\u6bb5\u6642\u9593\u5f8c\u91cd\u8a66<\/li>\n<li>\u91cd\u8907\u76f4\u5230\u6210\u529f\u6216\u8d85\u6642\uff08\u9810\u8a2d 1 \u79d2\uff09<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u5be6\u6230\uff1a\u6e2c\u8a66 API \u547c\u53eb\u5143\u4ef6<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u88ab\u6e2c\u8a66\u7684\u5143\u4ef6<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ UserProfile.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> { useState, useEffect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { fetchUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/api'<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">UserProfile<\/span>(<span class=\"hljs-params\">{ userId }: { userId: string }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;user, setUser] = useState(<span class=\"hljs-literal\">null<\/span>)\n  <span class=\"hljs-keyword\">const<\/span> &#91;loading, setLoading] = useState(<span class=\"hljs-literal\">true<\/span>)\n  <span class=\"hljs-keyword\">const<\/span> &#91;error, setError] = useState(<span class=\"hljs-literal\">null<\/span>)\n\n  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    fetchUser(userId)\n      .then(<span class=\"hljs-function\"><span class=\"hljs-params\">data<\/span> =&gt;<\/span> {\n        setUser(data)\n        setLoading(<span class=\"hljs-literal\">false<\/span>)\n      })\n      .catch(<span class=\"hljs-function\"><span class=\"hljs-params\">err<\/span> =&gt;<\/span> {\n        setError(err.message)\n        setLoading(<span class=\"hljs-literal\">false<\/span>)\n      })\n  }, &#91;userId])\n\n  <span class=\"hljs-keyword\">if<\/span> (loading) <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Loading...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  <span class=\"hljs-keyword\">if<\/span> (error) <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">role<\/span>=<span class=\"hljs-string\">\"alert\"<\/span>&gt;<\/span>{error}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Hello, {user.name}!<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u5b8c\u6574\u6e2c\u8a66\u7bc4\u4f8b<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ UserProfile.test.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> { render, screen } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@testing-library\/react'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { vi, expect, test, beforeEach } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'vitest'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { UserProfile } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/UserProfile'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { fetchUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/api'<\/span>\n\n<span class=\"hljs-comment\">\/\/ \u6a21\u64ec API \u6a21\u7d44<\/span>\nvi.mock(<span class=\"hljs-string\">'.\/api'<\/span>)\n\nbeforeEach(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-comment\">\/\/ \u6bcf\u500b\u6e2c\u8a66\u524d\u91cd\u7f6e mock<\/span>\n  vi.resetAllMocks()\n})\n\ntest(<span class=\"hljs-string\">'shows loading state initially'<\/span>, () =&gt; {\n  vi.mocked(fetchUser).mockResolvedValue({ <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Alice'<\/span> })\n\n  render(<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UserProfile<\/span> <span class=\"hljs-attr\">userId<\/span>=<span class=\"hljs-string\">\"1\"<\/span> \/&gt;<\/span><\/span>)\n\n  expect(screen.getByText(<span class=\"hljs-string\">'Loading...'<\/span>)).toBeInTheDocument()\n})\n\ntest(<span class=\"hljs-string\">'shows user name after loading'<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  vi.mocked(fetchUser).mockResolvedValue({ <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Alice'<\/span> })\n\n  render(<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UserProfile<\/span> <span class=\"hljs-attr\">userId<\/span>=<span class=\"hljs-string\">\"1\"<\/span> \/&gt;<\/span><\/span>)\n\n  <span class=\"hljs-comment\">\/\/ \u4f7f\u7528 findByText \u7b49\u5f85\u975e\u540c\u6b65\u8f09\u5165\u5b8c\u6210<\/span>\n  expect(<span class=\"hljs-keyword\">await<\/span> screen.findByText(<span class=\"hljs-string\">'Hello, Alice!'<\/span>)).toBeInTheDocument()\n})\n\ntest(<span class=\"hljs-string\">'shows error message on failure'<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  vi.mocked(fetchUser).mockRejectedValue(<span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">'Network error'<\/span>))\n\n  render(<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UserProfile<\/span> <span class=\"hljs-attr\">userId<\/span>=<span class=\"hljs-string\">\"1\"<\/span> \/&gt;<\/span><\/span>)\n\n  <span class=\"hljs-comment\">\/\/ \u7b49\u5f85\u932f\u8aa4\u8a0a\u606f\u51fa\u73fe<\/span>\n  <span class=\"hljs-keyword\">const<\/span> alert = <span class=\"hljs-keyword\">await<\/span> screen.findByRole(<span class=\"hljs-string\">'alert'<\/span>)\n  expect(alert).toHaveTextContent(<span class=\"hljs-string\">'Network error'<\/span>)\n})\n\ntest(<span class=\"hljs-string\">'calls fetchUser with correct userId'<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  vi.mocked(fetchUser).mockResolvedValue({ <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Bob'<\/span> })\n\n  render(<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UserProfile<\/span> <span class=\"hljs-attr\">userId<\/span>=<span class=\"hljs-string\">\"42\"<\/span> \/&gt;<\/span><\/span>)\n\n  <span class=\"hljs-keyword\">await<\/span> screen.findByText(<span class=\"hljs-string\">'Hello, Bob!'<\/span>)\n\n  <span class=\"hljs-comment\">\/\/ \u9a57\u8b49 API \u88ab\u6b63\u78ba\u547c\u53eb<\/span>\n  expect(fetchUser).toHaveBeenCalledWith(<span class=\"hljs-string\">'42'<\/span>)\n  expect(fetchUser).toHaveBeenCalledTimes(<span class=\"hljs-number\">1<\/span>)\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u9010\u4e00\u89e3\u8b80\u6bcf\u500b\u6e2c\u8a66<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u6e2c\u8a66 1\uff1a\u986f\u793a\u8f09\u5165\u72c0\u614b<\/strong><\/p>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">test(<span class=\"hljs-string\">'shows loading state initially'<\/span>, () =&gt; {\n  vi.mocked(fetchUser).mockResolvedValue({ <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Alice'<\/span> })\n  render(<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UserProfile<\/span> <span class=\"hljs-attr\">userId<\/span>=<span class=\"hljs-string\">\"1\"<\/span> \/&gt;<\/span><\/span>)\n  expect(screen.getByText(<span class=\"hljs-string\">'Loading...'<\/span>)).toBeInTheDocument()\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li>\u9019\u662f\u540c\u6b65\u6e2c\u8a66\uff0c\u56e0\u70ba\u6211\u5011\u53ea\u6aa2\u67e5\u300c\u521d\u59cb\u300d\u72c0\u614b<\/li>\n<li>\u5373\u4f7f API \u6703\u6210\u529f\u56de\u50b3\uff0c\u6211\u5011\u5728\u5b83\u5b8c\u6210\u524d\u5c31\u5df2\u7d93\u6aa2\u67e5\u5b8c\u4e86<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u6e2c\u8a66 2\uff1a\u8f09\u5165\u5b8c\u6210\u986f\u793a\u4f7f\u7528\u8005\u540d\u7a31<\/strong><\/p>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">test(<span class=\"hljs-string\">'shows user name after loading'<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  vi.mocked(fetchUser).mockResolvedValue({ <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Alice'<\/span> })\n  render(<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UserProfile<\/span> <span class=\"hljs-attr\">userId<\/span>=<span class=\"hljs-string\">\"1\"<\/span> \/&gt;<\/span><\/span>)\n  expect(<span class=\"hljs-keyword\">await<\/span> screen.findByText(<span class=\"hljs-string\">'Hello, Alice!'<\/span>)).toBeInTheDocument()\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li>\u4f7f\u7528 <code>async\/await<\/code> \u56e0\u70ba\u8981\u7b49\u5f85\u975e\u540c\u6b65\u64cd\u4f5c<\/li>\n<li><code>findByText<\/code> \u6703\u7b49\u5f85\u6587\u5b57\u51fa\u73fe<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u6e2c\u8a66 3\uff1a\u986f\u793a\u932f\u8aa4\u8a0a\u606f<\/strong><\/p>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">test(<span class=\"hljs-string\">'shows error message on failure'<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  vi.mocked(fetchUser).mockRejectedValue(<span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">'Network error'<\/span>))\n  render(<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UserProfile<\/span> <span class=\"hljs-attr\">userId<\/span>=<span class=\"hljs-string\">\"1\"<\/span> \/&gt;<\/span><\/span>)\n  <span class=\"hljs-keyword\">const<\/span> alert = <span class=\"hljs-keyword\">await<\/span> screen.findByRole(<span class=\"hljs-string\">'alert'<\/span>)\n  expect(alert).toHaveTextContent(<span class=\"hljs-string\">'Network error'<\/span>)\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li>\u4f7f\u7528 <code>mockRejectedValue<\/code> \u6a21\u64ec API \u5931\u6557<\/li>\n<li>\u900f\u904e <code>role=\"alert\"<\/code> \u4f86\u67e5\u8a62\u932f\u8aa4\u8a0a\u606f\u5143\u7d20<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u6e2c\u8a66 4\uff1a\u9a57\u8b49 API \u547c\u53eb<\/strong><\/p>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">test(<span class=\"hljs-string\">'calls fetchUser with correct userId'<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  vi.mocked(fetchUser).mockResolvedValue({ <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Bob'<\/span> })\n  render(<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UserProfile<\/span> <span class=\"hljs-attr\">userId<\/span>=<span class=\"hljs-string\">\"42\"<\/span> \/&gt;<\/span><\/span>)\n  <span class=\"hljs-keyword\">await<\/span> screen.findByText(<span class=\"hljs-string\">'Hello, Bob!'<\/span>)\n  expect(fetchUser).toHaveBeenCalledWith(<span class=\"hljs-string\">'42'<\/span>)\n  expect(fetchUser).toHaveBeenCalledTimes(<span class=\"hljs-number\">1<\/span>)\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li>\u7b49\u5f85\u8f09\u5165\u5b8c\u6210\u5f8c\uff0c\u9a57\u8b49 mock \u51fd\u6578\u88ab\u6b63\u78ba\u547c\u53eb<\/li>\n<li>\u78ba\u8a8d\u50b3\u5165\u7684\u53c3\u6578\u548c\u547c\u53eb\u6b21\u6578<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u5e38\u898b\u7684 Mock \u9a57\u8b49\u65b9\u6cd5<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u65b9\u6cd5<\/th>\n<th>\u8aaa\u660e<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>toHaveBeenCalled()<\/code><\/td>\n<td>\u51fd\u6578\u6709\u88ab\u547c\u53eb\u904e<\/td>\n<\/tr>\n<tr>\n<td><code>toHaveBeenCalledTimes(n)<\/code><\/td>\n<td>\u51fd\u6578\u88ab\u547c\u53eb\u4e86 n \u6b21<\/td>\n<\/tr>\n<tr>\n<td><code>toHaveBeenCalledWith(arg)<\/code><\/td>\n<td>\u51fd\u6578\u88ab\u547c\u53eb\u6642\u50b3\u5165\u4e86\u6307\u5b9a\u53c3\u6578<\/td>\n<\/tr>\n<tr>\n<td><code>toHaveBeenLastCalledWith(arg)<\/code><\/td>\n<td>\u6700\u5f8c\u4e00\u6b21\u547c\u53eb\u50b3\u5165\u4e86\u6307\u5b9a\u53c3\u6578<\/td>\n<\/tr>\n<tr>\n<td><code>toHaveBeenNthCalledWith(n, arg)<\/code><\/td>\n<td>\u7b2c n \u6b21\u547c\u53eb\u50b3\u5165\u4e86\u6307\u5b9a\u53c3\u6578<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u5e38\u898b\u7684\u975e\u540c\u6b65\u6e2c\u8a66\u6a21\u5f0f<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u6a21\u5f0f 1\uff1a\u7b49\u5f85\u5143\u7d20\u51fa\u73fe<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ \u4f7f\u7528 findBy*\uff08\u63a8\u85a6\uff09<\/span>\n<span class=\"hljs-keyword\">const<\/span> element = <span class=\"hljs-keyword\">await<\/span> screen.findByText(<span class=\"hljs-string\">'Success'<\/span>)\n\n<span class=\"hljs-comment\">\/\/ \u6216\u4f7f\u7528 waitFor<\/span>\n<span class=\"hljs-keyword\">await<\/span> waitFor(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  expect(screen.getByText(<span class=\"hljs-string\">'Success'<\/span>)).toBeInTheDocument()\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u6a21\u5f0f 2\uff1a\u7b49\u5f85\u5143\u7d20\u6d88\u5931<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">await<\/span> waitFor(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  expect(screen.queryByText(<span class=\"hljs-string\">'Loading...'<\/span>)).not.toBeInTheDocument()\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u6a21\u5f0f 3\uff1a\u7b49\u5f85\u591a\u500b\u689d\u4ef6<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">await<\/span> waitFor(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  expect(screen.queryByText(<span class=\"hljs-string\">'Loading...'<\/span>)).not.toBeInTheDocument()\n  expect(screen.getByText(<span class=\"hljs-string\">'Data loaded'<\/span>)).toBeInTheDocument()\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u91cd\u9ede\u6574\u7406<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>vi.fn()<\/strong>\uff1a\u5efa\u7acb\u53ef\u8ffd\u8e64\u7684 mock \u51fd\u6578\n<ul>\n<li>\u8a18\u9304\u547c\u53eb\u6b21\u6578\u548c\u53c3\u6578<\/li>\n<li>\u53ef\u8a2d\u5b9a\u56de\u50b3\u503c\uff08<code>mockReturnValue<\/code>\u3001<code>mockResolvedValue<\/code>\uff09<\/li>\n<\/ul>\n<\/li>\n<li><strong>vi.mock()<\/strong>\uff1a\u6a21\u64ec\u6574\u500b\u6a21\u7d44\n<ul>\n<li>\u81ea\u52d5\u5c07\u6a21\u7d44\u4e2d\u7684\u51fd\u6578\u66ff\u63db\u70ba mock<\/li>\n<li>\u4f7f\u7528 <code>vi.mocked()<\/code> \u53d6\u5f97\u6b63\u78ba\u7684 TypeScript \u578b\u5225<\/li>\n<\/ul>\n<\/li>\n<li><strong>\u975e\u540c\u6b65\u6e2c\u8a66<\/strong>\uff1a\n<ul>\n<li><code>findBy*<\/code>\uff1a\u7b49\u5f85\u5143\u7d20\u51fa\u73fe<\/li>\n<li><code>waitFor<\/code>\uff1a\u7b49\u5f85\u4efb\u610f\u689d\u4ef6\u6210\u7acb<\/li>\n<li>\u8a18\u5f97\u4f7f\u7528 <code>async\/await<\/code><\/li>\n<\/ul>\n<\/li>\n<li><strong>\u6e2c\u8a66\u524d\u91cd\u7f6e<\/strong>\uff1a\n<ul>\n<li>\u4f7f\u7528 <code>beforeEach<\/code> \u548c <code>vi.resetAllMocks()<\/code> \u78ba\u4fdd\u6e2c\u8a66\u7368\u7acb<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u4e0b\u4e00\u6b65<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u5728\u6700\u5f8c\u4e00\u7bc7\u6587\u7ae0\u4e2d\uff0c\u6211\u5011\u5c07\u5b78\u7fd2\u6e2c\u8a66 Custom Hook \u548c Context\uff0c\u9019\u662f\u6e2c\u8a66 React \u61c9\u7528\u4e2d\u5171\u4eab\u908f\u8f2f\u7684\u95dc\u9375\u6280\u80fd\u3002<\/p>\n\n\n\n<div id=\"quiz-advanced\">\n<style>\n#quiz-advanced .badge {\n            display: inline-block;\n            padding: 4px 10px;\n            border-radius: 12px;\n            font-size: 0.75em;\n            font-weight: bold;\n            margin-left: 8px;\n        }#quiz-advanced .badge.scenario { background: #ebf8ff; color: #2b6cb0; }#quiz-advanced .badge.debug { background: #fef3c7; color: #92400e; }#quiz-advanced .question-adv {\n            background: white;\n            border-radius: 10px;\n            padding: 24px;\n            margin: 20px 0;\n            box-shadow: 0 2px 8px rgba(0,0,0,0.08);\n            border-left: 4px solid #ed8936;\n        }#quiz-advanced .question-adv h3 { margin-top: 0; color: #2d3748; }#quiz-advanced .context {\n            background: #f7fafc;\n            border: 1px solid #e2e8f0;\n            border-radius: 6px;\n            padding: 14px;\n            margin: 12px 0;\n            font-family: 'Menlo', 'Monaco', 'Courier New', monospace;\n            font-size: 0.9em;\n            white-space: pre-wrap;\n            overflow-x: auto;\n        }#quiz-advanced .options-adv { list-style: none; padding: 0; }#quiz-advanced .options-adv li {\n            padding: 14px 16px;\n            margin: 10px 0;\n            border: 2px solid #e2e8f0;\n            border-radius: 8px;\n            cursor: pointer;\n            transition: all 0.2s;\n        }#quiz-advanced .options-adv li:hover:not(.disabled) {\n            border-color: #ed8936;\n            background: #fffaf0;\n        }#quiz-advanced .options-adv li.disabled { cursor: default; opacity: 0.7; }#quiz-advanced .options-adv li.show-answer { border-color: #48bb78; background: #f0fff4; }#quiz-advanced .options-adv li.wrong { border-color: #fc8181; background: #fff5f5; }#quiz-advanced .feedback-adv {\n            margin-top: 16px;\n            padding: 14px;\n            border-radius: 8px;\n            display: none;\n            line-height: 1.6;\n        }#quiz-advanced .feedback-adv.correct { background: #f0fff4; color: #276749; display: block; }#quiz-advanced .feedback-adv.wrong { background: #fff5f5; color: #c53030; display: block; }#quiz-advanced .feedback-adv \n        .score-adv {\n            text-align: center;\n            font-size: 1.4em;\n            padding: 24px;\n            background: white;\n            border-radius: 10px;\n            margin-top: 24px;\n            box-shadow: 0 2px 8px rgba(0,0,0,0.08);\n        }#quiz-advanced .intro {\n            background: #ebf8ff;\n            padding: 16px;\n            border-radius: 8px;\n            margin-bottom: 20px;\n            color: #2c5282;\n        }\n<\/style>\n<h1>\u9032\u968e\u6e2c\u9a57\uff1aMock \u8207\u975e\u540c\u6b65\u6e2c\u8a66<\/h1>\n    <div class=\"intro\">\n        <strong>\u6e2c\u9a57\u76ee\u6a19\uff1a<\/strong>\u9a57\u8b49\u4f60\u662f\u5426\u80fd\u5728\u5be6\u969b\u60c5\u5883\u4e2d\u61c9\u7528\u6240\u5b78\u3002<br>\n        \u5171 5 \u984c\uff0c\u5305\u542b\u60c5\u5883\u984c\u8207\u932f\u8aa4\u8a3a\u65b7\u984c\u3002\n    <\/div>\n\n    <div class=\"question-adv\" id=\"qAdv1\">\n        <h3>1. \u6e2c\u8a66 UserProfile \u5143\u4ef6\u6642\uff0c\u4f60\u60f3\u9a57\u8b49 API \u547c\u53eb\u5931\u6557\u6642\u6703\u986f\u793a\u932f\u8aa4\u8a0a\u606f\u3002\u61c9\u8a72\u5982\u4f55\u8a2d\u5b9a mock\uff1f <span class=\"badge scenario\">\u60c5\u5883\u984c<\/span><\/h3>\n        <div class=\"context\">\/\/ UserProfile \u5143\u4ef6\u5728 API \u5931\u6557\u6642\u6703\u986f\u793a &lt;div role=&#8221;alert&#8221;&gt;{error.message}&lt;\/div&gt;\nvi.mock(&#8216;.\/api&#8217;)\n\ntest(&#8216;shows error on API failure&#8217;, async () => {\n  \/\/ \u5982\u4f55\u8a2d\u5b9a fetchUser \u7684 mock\uff1f\n  render(&lt;UserProfile userId=&#8221;1&#8243; \/&gt;)\n  const alert = await screen.findByRole(&#8216;alert&#8217;)\n  expect(alert).toHaveTextContent(&#8216;Network error&#8217;)\n})<\/div>\n        <ul class=\"options-adv\">\n            <li onclick=\"checkAnswerAdv(1, 'A', 'C')\">A. <code>vi.mocked(fetchUser).mockReturnValue(new Error('Network error'))<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(1, 'B', 'C')\">B. <code>vi.mocked(fetchUser).mockResolvedValue({ error: 'Network error' })<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(1, 'C', 'C')\">C. <code>vi.mocked(fetchUser).mockRejectedValue(new Error('Network error'))<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(1, 'D', 'C')\">D. <code>vi.mocked(fetchUser).mockThrow(new Error('Network error'))<\/code><\/li>\n        <\/ul>\n        <div class=\"feedback-adv\" id=\"fbAdv1\"><\/div>\n    <\/div>\n\n    <div class=\"question-adv\" id=\"qAdv2\">\n        <h3>2. \u5c0f\u660e\u7684\u6e2c\u8a66\u5931\u6557\u4e86\uff0c\u932f\u8aa4\u8a0a\u606f\u662f\u300cUnable to find an element with the text: Hello, Alice!\u300d\u3002\u554f\u984c\u51fa\u5728\u54ea\u88e1\uff1f <span class=\"badge debug\">\u932f\u8aa4\u8a3a\u65b7<\/span><\/h3>\n        <div class=\"context\">test(&#8216;shows user name after loading&#8217;, () => {\n  vi.mocked(fetchUser).mockResolvedValue({ name: &#8216;Alice&#8217; })\n\n  render(&lt;UserProfile userId=&#8221;1&#8243; \/&gt;)\n\n  expect(screen.getByText(&#8216;Hello, Alice!&#8217;)).toBeInTheDocument()\n})<\/div>\n        <ul class=\"options-adv\">\n            <li onclick=\"checkAnswerAdv(2, 'A', 'B')\">A. mock \u7684\u56de\u50b3\u503c\u683c\u5f0f\u932f\u8aa4<\/li>\n            <li onclick=\"checkAnswerAdv(2, 'B', 'B')\">B. \u61c9\u8a72\u4f7f\u7528 <code>findByText<\/code> \u4e26\u52a0\u4e0a <code>await<\/code>\uff0c\u56e0\u70ba\u8cc7\u6599\u8f09\u5165\u662f\u975e\u540c\u6b65\u7684<\/li>\n            <li onclick=\"checkAnswerAdv(2, 'C', 'B')\">C. \u9700\u8981\u5148\u547c\u53eb <code>vi.resetAllMocks()<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(2, 'D', 'B')\">D. <code>render<\/code> \u51fd\u6578\u9700\u8981\u52a0\u4e0a <code>await<\/code><\/li>\n        <\/ul>\n        <div class=\"feedback-adv\" id=\"fbAdv2\"><\/div>\n    <\/div>\n\n    <div class=\"question-adv\" id=\"qAdv3\">\n        <h3>3. \u4f60\u60f3\u6e2c\u8a66\u6309\u9215\u9ede\u64ca\u5f8c\u6703\u547c\u53eb\u6b63\u78ba\u7684 callback \u51fd\u6578\u3002\u4ee5\u4e0b\u54ea\u500b\u5beb\u6cd5\u6700\u5b8c\u6574\uff1f <span class=\"badge scenario\">\u60c5\u5883\u984c<\/span><\/h3>\n        <div class=\"context\">function SubmitButton({ onSubmit }) {\n  return &lt;button onClick={() => onSubmit(&#8216;data&#8217;)}&gt;Submit&lt;\/button&gt;\n}<\/div>\n        <ul class=\"options-adv\">\n            <li onclick=\"checkAnswerAdv(3, 'A', 'D')\">A. <code>const handleSubmit = () => {}; render(&lt;SubmitButton onSubmit={handleSubmit} \/&gt;)<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(3, 'B', 'D')\">B. <code>const handleSubmit = vi.fn(); expect(handleSubmit).toBeCalled()<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(3, 'C', 'D')\">C. <code>render(&lt;SubmitButton \/&gt;); await user.click(screen.getByRole('button'))<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(3, 'D', 'D')\">D. <code>const handleSubmit = vi.fn(); await user.click(button); expect(handleSubmit).toHaveBeenCalledWith('data')<\/code><\/li>\n        <\/ul>\n        <div class=\"feedback-adv\" id=\"fbAdv3\"><\/div>\n    <\/div>\n\n    <div class=\"question-adv\" id=\"qAdv4\">\n        <h3>4. \u6e2c\u8a66\u7a0b\u5f0f\u78bc\u5728\u6bcf\u500b\u6e2c\u8a66\u9593\u4e92\u76f8\u5f71\u97ff\uff0c\u7b2c\u4e8c\u500b\u6e2c\u8a66\u7e3d\u662f\u5931\u6557\u3002\u5982\u4f55\u4fee\u6b63\uff1f <span class=\"badge debug\">\u932f\u8aa4\u8a3a\u65b7<\/span><\/h3>\n        <div class=\"context\">vi.mock(&#8216;.\/api&#8217;)\n\ntest(&#8216;test 1: success case&#8217;, async () => {\n  vi.mocked(fetchUser).mockResolvedValue({ name: &#8216;Alice&#8217; })\n  render(&lt;UserProfile userId=&#8221;1&#8243; \/&gt;)\n  await screen.findByText(&#8216;Hello, Alice!&#8217;)\n})\n\ntest(&#8216;test 2: error case&#8217;, async () => {\n  vi.mocked(fetchUser).mockRejectedValue(new Error(&#8216;Error&#8217;))\n  render(&lt;UserProfile userId=&#8221;1&#8243; \/&gt;)\n  \/\/ \u9019\u88e1\u7e3d\u662f\u627e\u4e0d\u5230 alert\uff0c\u56e0\u70ba\u9084\u662f\u986f\u793a Alice\n  await screen.findByRole(&#8216;alert&#8217;)\n})<\/div>\n        <ul class=\"options-adv\">\n            <li onclick=\"checkAnswerAdv(4, 'A', 'C')\">A. \u9700\u8981\u628a\u5169\u500b\u6e2c\u8a66\u653e\u5728\u4e0d\u540c\u7684\u6a94\u6848<\/li>\n            <li onclick=\"checkAnswerAdv(4, 'B', 'C')\">B. \u61c9\u8a72\u5728\u6bcf\u500b\u6e2c\u8a66\u5f8c\u547c\u53eb <code>cleanup()<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(4, 'C', 'C')\">C. \u9700\u8981\u5728 <code>beforeEach<\/code> \u4e2d\u547c\u53eb <code>vi.resetAllMocks()<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(4, 'D', 'C')\">D. \u61c9\u8a72\u4f7f\u7528 <code>vi.fn()<\/code> \u800c\u4e0d\u662f <code>vi.mock()<\/code><\/li>\n        <\/ul>\n        <div class=\"feedback-adv\" id=\"fbAdv4\"><\/div>\n    <\/div>\n\n    <div class=\"question-adv\" id=\"qAdv5\">\n        <h3>5. \u4f60\u60f3\u7b49\u5f85\u300cLoading&#8230;\u300d\u6587\u5b57\u6d88\u5931\u5f8c\u518d\u9032\u884c\u65b7\u8a00\uff0c\u61c9\u8a72\u5982\u4f55\u5beb\uff1f <span class=\"badge scenario\">\u60c5\u5883\u984c<\/span><\/h3>\n        <div class=\"context\">\/\/ \u5143\u4ef6\u8f09\u5165\u6642\u986f\u793a Loading&#8230;\uff0c\u5b8c\u6210\u5f8c\u986f\u793a\u8cc7\u6599\nrender(&lt;AsyncComponent \/&gt;)\n\n\/\/ \u5982\u4f55\u7b49\u5f85 Loading&#8230; \u6d88\u5931\uff1f<\/div>\n        <ul class=\"options-adv\">\n            <li onclick=\"checkAnswerAdv(5, 'A', 'B')\">A. <code>await screen.findByText('Loading...', { hidden: true })<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(5, 'B', 'B')\">B. <code>await waitFor(() => { expect(screen.queryByText('Loading...')).not.toBeInTheDocument() })<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(5, 'C', 'B')\">C. <code>await screen.getByText('Loading...').waitForRemoval()<\/code><\/li>\n            <li onclick=\"checkAnswerAdv(5, 'D', 'B')\">D. <code>expect(screen.findByText('Loading...')).toBeNull()<\/code><\/li>\n        <\/ul>\n        <div class=\"feedback-adv\" id=\"fbAdv5\"><\/div>\n    <\/div>\n\n    <div class=\"score-adv\" id=\"scoreAdv\" style=\"display:none;\"><\/div>\n<script>\n\n        let answeredAdv = 0;\n        let correctAdv = 0;\n        const totalAdv = 5;\n        const explanationsAdv = {\n            1: \"\u6a21\u64ec Promise \u5931\u6557\u8981\u7528 <code>mockRejectedValue()<\/code>\u3002<code>mockReturnValue()<\/code> \u662f\u540c\u6b65\u56de\u50b3\uff0c<code>mockResolvedValue()<\/code> \u662f Promise \u6210\u529f\u3002\u6c92\u6709 <code>mockThrow<\/code> \u9019\u500b\u65b9\u6cd5\u3002\",\n            2: \"\u56e0\u70ba <code>fetchUser<\/code> \u662f\u975e\u540c\u6b65\u7684\uff0c\u5143\u4ef6\u9700\u8981\u6642\u9593\u8f09\u5165\u8cc7\u6599\u3002<code>getByText<\/code> \u662f\u540c\u6b65\u67e5\u8a62\uff0c\u6703\u5728\u8cc7\u6599\u8f09\u5165\u5b8c\u6210\u524d\u5c31\u57f7\u884c\u4e26\u5931\u6557\u3002\u61c9\u8a72\u6539\u7528 <code>await screen.findByText('Hello, Alice!')<\/code> \u7b49\u5f85\u5143\u7d20\u51fa\u73fe\u3002\",\n            3: \"\u5b8c\u6574\u7684\u6e2c\u8a66\u9700\u8981\uff1a(1) \u7528 <code>vi.fn()<\/code> \u5efa\u7acb\u53ef\u8ffd\u8e64\u7684 mock \u51fd\u6578\u3001(2) \u6a21\u64ec\u4f7f\u7528\u8005\u4e92\u52d5\u3001(3) \u9a57\u8b49\u51fd\u6578\u88ab\u547c\u53eb\u4e26\u6aa2\u67e5\u50b3\u5165\u7684\u53c3\u6578\u3002\u9078\u9805 D \u5305\u542b\u4e86\u6240\u6709\u5fc5\u8981\u5143\u7d20\u3002\",\n            4: \"Mock \u51fd\u6578\u7684\u72c0\u614b\u6703\u5728\u6e2c\u8a66\u9593\u4fdd\u7559\u3002\u4f7f\u7528 <code>beforeEach(() => { vi.resetAllMocks() })<\/code> \u53ef\u4ee5\u5728\u6bcf\u500b\u6e2c\u8a66\u524d\u91cd\u7f6e\u6240\u6709 mock\uff0c\u78ba\u4fdd\u6e2c\u8a66\u7368\u7acb\u3002<code>cleanup()<\/code> \u662f\u6e05\u7406 DOM\uff0c\u8207 mock \u7121\u95dc\u3002\",\n            5: \"\u7b49\u5f85\u5143\u7d20\u6d88\u5931\u8981\u7528 <code>waitFor<\/code> \u642d\u914d <code>queryByText<\/code>\uff08\u4e0d\u5b58\u5728\u6642\u56de\u50b3 null\uff09\u548c <code>not.toBeInTheDocument()<\/code>\u3002<code>findByText<\/code> \u662f\u7528\u4f86\u7b49\u5f85\u5143\u7d20\u300c\u51fa\u73fe\u300d\u800c\u975e\u6d88\u5931\u3002\"\n        };\n\n        function checkAnswerAdv(qNum, selected, correctAns) {\n            const question = document.getElementById('qAdv' + qNum);\n            if (question.classList.contains('answered')) return;\n\n            question.classList.add('answered');\n            answeredAdv++;\n\n            const options = question.querySelectorAll('.options-adv li');\n            options.forEach(opt => {\n                opt.classList.add('disabled');\n                if (opt.textContent.startsWith(correctAns + '.')) {\n                    opt.classList.add('show-answer');\n                }\n            });\n\n            const feedback = document.getElementById('fbAdv' + qNum);\n            if (selected === correctAns) {\n                correctAdv++;\n                feedback.className = 'feedback-adv correct';\n                feedback.innerHTML = '\u2713 \u6b63\u78ba\uff01' + explanationsAdv[qNum];\n            } else {\n                feedback.className = 'feedback-adv wrong';\n                feedback.innerHTML = '\u2717 \u932f\u8aa4\u3002\u6b63\u78ba\u7b54\u6848\u662f ' + correctAns + '\u3002' + explanationsAdv[qNum];\n                options.forEach(opt => {\n                    if (opt.textContent.startsWith(selected + '.')) {\n                        opt.classList.add('wrong');\n                    }\n                });\n            }\n\n            if (answeredAdv === totalAdv) {\n                const scoreDiv = document.getElementById('scoreAdv');\n                scoreDiv.style.display = 'block';\n                let msg = '';\n                if (correctAdv === totalAdv) msg = '\ud83c\udfaf \u5b8c\u7f8e\uff01\u4f60\u5df2\u7d93\u6e96\u5099\u597d\u5be6\u6230\u4e86\uff01';\n                else if (correctAdv >= 4) msg = '\ud83d\udcaa \u5f88\u68d2\uff01\u518d\u8907\u7fd2\u4e00\u4e0b\u932f\u7684\u984c\u76ee';\n                else if (correctAdv >= 3) msg = '\ud83d\udcd6 \u5efa\u8b70\u91cd\u8b80\u76f8\u95dc\u6bb5\u843d\u5f8c\u518d\u8a66\u4e00\u6b21';\n                else msg = '\ud83d\udd04 \u5efa\u8b70\u91cd\u65b0\u95b1\u8b80\u6587\u4ef6\uff0c\u7279\u5225\u6ce8\u610f\u64cd\u4f5c\u6d41\u7a0b';\n                scoreDiv.innerHTML = '\u6e2c\u9a57\u5b8c\u6210\uff01\u5f97\u5206\uff1a' + correctAdv + ' \/ ' + totalAdv + '<br>' + msg;\n            }\n        }\n    \n<\/script>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>\u6e2c\u9a57\uff1aMock \u8207\u975e\u540c\u6b65\u6e2c\u8a66 \u5171 5 \u984c\uff0c\u9ede\u9078\u7b54\u6848\u5f8c\u6703\u7acb\u5373\u986f\u793a\u7d50\u679c 1. vi.fn() \u5efa\u7acb\u7684 mock \u51fd [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rolpb_likes":0,"footnotes":""},"categories":[340],"tags":[347,269,348,349,341,350,351],"class_list":["post-360","post","type-post","status-publish","format-standard","hentry","category-vitest","tag-mock","tag-react","tag-vi-fn","tag-vi-mock","tag-vitest","tag-waitfor","tag-351"],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":7}},"_links":{"self":[{"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/posts\/360","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/comments?post=360"}],"version-history":[{"count":0,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/posts\/360\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/media?parent=360"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/categories?post=360"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/tags?post=360"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}